用CSS transitions实现动画式下拉菜单

标签:CSS

在CSS3中主要有3种实现动画的方式:transform、transition和animation。其中,transform是改变形状,例如伸缩和翻转等(本站上方的那几个图标就用了这个属性来翻转);transition是实现渐变效果,将原本只能瞬间完成的变换,在一个指定的时间段里慢慢展现出来(那几个图标翻转的渐变过程就是用这个属性来设置的);而animation则是最为强大的动画功能,通过定义关键帧和时间,浏览器可以自动完成关键帧之间的演变(本站曾用过的koi皮肤中,那个不停翻滚的feed图标就是用这个属性来实现的)。

昨天在V2EX看到有人用CSS3特效做页面菜单,其实主要就是用transition这个属性。于是想着本站右上角那个“热门文章”的菜单是否也能弄成动画效果呢,便也尝试了一番,做出了目前这种效果。
不过拿它来讲解也蛮复杂的,于是还是从入门的开始,先做个不依赖JavaScript的没有动画效果的下拉菜单。
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
ul, li {
	list-style: none;
	margin: 0;
	padding: 0;
}
#nav a {
	display: inline-block;
	background: #eee;
	padding: 3px;
	margin: 3px;
	width: 100px;
	text-decoration: none;
}
#nav a:hover {
	background: #aaa;
}
#nav li {
	position: relative;
}
#nav>li {
	display: inline-block;
}
#nav ul {
	display: none;
	position: absolute;
}
#nav li:hover>ul {
	display: block;
}
#nav ul ul {
	left: 110px;
	top: 0;
}
</style>
</head>
<body>
	<ul id="nav">
		<li><a href="#">1</a></li>
		<li>
			<a href="#">2</a>
			<ul>
				<li>
					<a href="#">2.1</a>
					<ul>
						<li><a href="#">2.1.1</a></li>
						<li><a href="#">2.1.2</a></li>
					</ul>
				</li>
				<li>
					<a href="#">2.2</a>
					<ul>
						<li><a href="#">2.2.1</a></li>
						<li>
							<a href="#">2.2.2</a>
							<ul>
								<li><a href="#">2.2.1</a></li>
								<li><a href="#">2.2.2</a></li>
								<li><a href="#">2.2.3</a></li>
							</ul>
						</li>
					</ul>
				</li>
				<li><a href="#">2.3</a></li>
			</ul>
		</li>
		<li>
			<a href="#">3</a>
			<ul>
				<li><a href="#">3.1</a></li>
				<li><a href="#">3.2</a></li>
			</ul>
		</li>
		<li><a href="#">4</a></li>
	</ul>
</body>
</html>

效果怎样我就不做个页面来演示了,借口也不找了,反正我就是懒。也不想管其他浏览器怎样,我主要用Chrome开发。
简单说下原理:
  1. 首先是将第一级菜单的菜单项(#nav>li)设为横排排列(display: inline-block或float: left都可)。
  2. 接着将第二级菜单及其子菜单(#nav ul)隐藏(display: none),而在鼠标悬停在菜单项上时(#nav li:hover>ul),显示下级子菜单(display: block)。
  3. 最后调整一下ul和li的position(分别为absolute和relative),让三级或更低级菜单(#nav ul ul)可以向右延伸(left: 110px;top: 0)。
当然,实现方法并不只有这种,但是原理都差不多,自己按照喜欢的写法去做吧。

接下来就该让它动起来了,先来试试对display属性进行渐变:
#nav ul {
	-webkit-transition: display 0.3s 0.1s ease-out;
}
这里我求方便只写了-webkit-transition,要支持其他浏览器的话,还得补上-moz、-o和-ms等。
这种写法其实是4个属性的缩写,分别是:
  1. transition-property:设定哪些属性的变化需要渐变,上面是针对display属性。
  2. transition-duration:渐变持续的时间。
  3. transition-delay:等待多久以后再开始变化,省略则为0。
  4. transition-timing-function:设置渐变过程中,每个阶段快慢的函数。它是一个贝塞尔曲线函数,有ease、linear、ease-in、ease-out和ease-in-out这5个预定义的值,详细解释可以参考MDN

现在再刷新试试我们的例子,你会发现什么动画效果都没有⋯至于原因嘛,估计是display从none到block不是一个可视的渐变过程,真正可渐变的应该是opacity。
于是再次改动代码:
#nav ul {
	/* display: none; */
	opacity: 0;
	position: absolute;
	-webkit-transition: opacity 0.3s 0.1s ease-out;
}
#nav li:hover>ul {
	/* display: block; */
	opacity: 1;
}

这次刷新后,终于能看到效果了。可是当鼠标移动到一些空白的地方也会显示菜单,这就有bug了。解决办法就是用visibility属性,它同样也得应用transition,且时间和opacity的相同,否则在opacity渐变时,可能因visibility为hidden而看不到动画效果。
#nav ul {
	visibility: hidden;
	opacity: 0;
	position: absolute;
	-webkit-transition: opacity 0.3s 0.1s ease-out, visibility 0.3s 0.1s linear;
}
#nav li:hover>ul {
	visibility: visible;
	opacity: 1;
}
这里用了不同的函数,在不透明度变化时,我希望用户能很快看到菜单的出现,然后慢慢完全清晰地显示出来,所以可以用ease-out(感觉更好的方式是在:hover时也定义ease-in);而visibility其实也是没有可视的渐变过程的,所以用什么都无所谓。

单纯渐显的菜单也没什么意思,所以再来改变一下高度吧:
#nav li li {
	height: 0;
	-webkit-transition: height 0.3s 0.1s ease-out;
}
#nav li:hover>ul>li {
	height: 28px;
}

现在看上去效果不错,可是三级菜单不适合向下展开,最好是向右展开,于是再改下:
#nav li li li {
	height: 28px;
}
#nav ul ul {
	left: 0;
	top: 0;
	-webkit-transition: left 0.3s 0.1s ease-out;
}
#nav ul li:hover>ul {
	left: 110px;
	top: 0;
	z-index: -1;
}
这里唯一需要提的是z-index: -1,如果不设置的话,子菜单会覆盖在父菜单上面,获得鼠标焦点,再往右移动,这样鼠标焦点就跑到子菜单上去了。我不清楚有没有更好的解决办法,反正这样是可以搞定的。

我想看到这里,大家应该懂得怎么用transition了。它很简单好用,能高效地完成以前只能用JavaScript做到的动画效果;不过兼容性还是个问题,至少我在iOS设备上就不能用tap来触发:hover和:active的transition效果,别说动画效果了,连最终效果都没了⋯⋯

0条评论 你不来一发么↓

    想说点什么呢?