Flexbox中动画内幕

本文由大漠根据的《Animating flexboxes: the lowdown》所译,整个译文带有我们自己的理解与思想,如果译得不好或不对之处还请同行朋友指点。如需转载此译文,需注明英文出处:http://dev.opera.com/articles/view/animating-flexboxes-the-lowdown,以及作者相关信息

——作者:

——译者:大漠

简介

最近我在致力于弹性盒模型(flexbox)、动画(animations)和过渡(transition)几个属性的组合运用。我得出一个结论,对于简单的手风琴UI效果(如下图所示),这可能是非常有用的。当然,它不能在所有浏览器下正常的工作,但是你可以使用Modernizr库来支持那些老式的浏览器。

Flexbox中动画内幕

使用flexbox、transitions和一点javascript制作的一个简单的手风琴UI效果,具体效果可以点击这里查看。

接下来我们一起来探索。

一个简单无味的测试

在最初的测试中,我创建了一个简单的结构,在<section>标签中包含了三个<article>标签,如下所示:

<section>
  <article></article>
  <article></article>
  <article></article>
</section>	

我使用的CSS如下所示:

section {
  display: -webkit-flex;
  display: -moz-flex;
  display: -ms-flex;
  display: flex;
}

article {
  height: 300px;
  /*伸缩项目该占用的剩余空间比例:1/3*/
  -webkit-flex: 1;
  -moz-flex: 1;
  -ms-flex: 1;
  flex: 1;
  border: 1px solid black;
  -webkit-transition: 1s all;
  -moz-transition: 1s all;
  -ms-transition: 1s all;
  transition: 1s all;
}

article:hover {
  /*伸缩项目该占用的剩余空间比例:2/3*/	
  -webkit-flex: 2;
  -moz-flex: 2;
  -ms-flex: 2;
  flex: 2;
}	

很高兴的看到上面的例子在opera和chrome下运行的很完美(firefox和IE不支持flexbox的新语法),鼠标悬浮在<articles>上时能平滑的扩大,相邻的也能自动的缩小适合<section>容器大小。如果你想看到效果,请使用oprea和chrome浏览器狠狠的点击这里

一个更好看的flexbox过渡例子

根据上面的例子,我创建了一个更好看的例子(如下图所示),我们继续往下玩。

Flexbox中动画内幕

flexbox和transition创建的一个简单的手风琴UI效果,点击这里查看效果。

HTML结构基本上相同,除了添加更多的内容之外,在以前的基础上增加了几个<articles>标签,大约从三个变成了五个,另外把<section>标签删除了,直接使用<body>标签来代替了<section>标签来做为外面的容器。我使用下面的样式制作出同样效果的手风琴(加了一些基本的文字样式,为了简便而在这里省略没显示出来):

body {
  margin: 0 auto;
  display: -webkit-flex;
  display: -moz-flex;
  display: -ms-flex;
  display: flex;
}

article {  
  -webkit-flex: 1 0;
  -moz-flex: 1 0;
  -ms-flex: 1 0;
  flex: 1 0;
  -webkit-transition: 1s all;
  -moz-transition: 1s all;
  -ms-transition: 1s all;
  transition: 1s all;
  overflow: hidden;
  height: 550px;
  color: rgba(0,0,0,1);
}

article:hover {
  -webkit-flex: 1 600px;
  -moz-flex: 1 600px;
  -ms-flex: 1 600px;
  flex: 1 600px;
  color: rgba(0,0,0,1);
}	

为了大家更好的看到整个效果使用的样式代码,我将所有代码全部贴在此处,以供大家参考:

html {
  font-size: 10px;
  font-family: sans-serif;
}

h2 {
  font-size: 3.2rem;
  display: none;
}

p {
  font-size: 1.4rem;
}

body {
  
  margin: 0 auto;
  
  display: -webkit-flex;
  display: -moz-flex;
  display: flex;
}

article {  
      -webkit-flex: 1 0;
      -moz-flex: 1 0;
  flex: 1 0;
  
  margin: 0.5rem;
  padding: 1rem;
  border-radius: 1rem;
  color: rgba(0,0,0,0);
  
  background: -webkit-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: -moz-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: -ms-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: linear-gradient(to bottom right,rgba(0,0,0,0),rgba(0,0,0,0.4));
  
  -webkit-transition: 1s all;
  -moz-transition: 1s all;
  -o-transition: 1s all;
  transition: 1s all;
  
  overflow: hidden;
  height: 550px;
}

    article:hover {
  -webkit-flex: 1 600px;
  -moz-flex: 1 600px;
  flex: 1 600px;
  
  color: rgba(0,0,0,1);
}

article:nth-child(2n) {
  background-color: #f00;
}

article:nth-child(2n+1) {
  background-color: #aaf;
}	

——大漠

注:这里把标题h2隐藏了,那是因为flex动画在小窗口听情况之下会存在一个问题,无法自动的扩展列表的宽度。但不用担心,我将会在后面的文章中介绍如何解决这个问题。

一个animation/flexbox的例子

接下来试着创建一个类似的例子,但这个例子中是使用javacript制作的动画效果,并且运用到<article>上。这样做的主要目的是让用户能通过点击来展开内容而不是靠悬浮状态来控制内容的展开与收缩。

我写的这个例子和前面展示的例子非常相似,只不过我的例子中没使用过渡效果,而是通过ainmation定义了一个动效果,用来控制宽度的增加与减少。

@keyframes flex-out {
  0% {	  
    color: rgba(0,0,0,0);
    width: auto;
  }
  
  100% {  
    color: rgba(0,0,0,1);
    width: 500px;
  }
}

@keyframes flex-in {
  0% {	  
    color: rgba(0,0,0,1);
    width: 500px;
  }
  
  100% {  
    color: rgba(0,0,0,0);
    width: auto;
  }
}	

注:上面的例子中,没有添加各浏览器厂商的前缀,如果大家测试自己的代码,需要加上各浏览器的前缀才会有效显示。

是的,这里有一个关键区别。在我的例子中没有使用动画的flex。为什么呢?仅因为他不能正常工作吗?Flex可以有过渡效果,但不具有动画效果,因此我们要通过动态来改变宽度,动画用到下面的类上,如下所示:

/*扩展*/	
article.flex-out {
  -webkit-animation: flex-out 1s both;
  -moz-animation: flex-out 1s both;
  -ms-animation: flex-out 1s both;
  animation: flex-out 1s both;
}
/*收缩*/
article.flex-in {
  -webkit-animation: flex-in 1s both;
  -moz-animation: flex-in 1s both;
  -ms-animation: flex-in 1s both;
  animation: flex-in 1s both;
}	

然后通过一个简单的js给<article>切换类名“flex-out”和“flex-in”,这样在点击时允许我产展开和折叠内容,达到我们想要的效果。

var articles = document.getElementsByTagName('article');

var toggleFlex = function(articleId) {
  for(j=0;j<=(articles.length-1);j++) {
    if(articles[j].id == articleId) {
      if(articles[j].className == "" || articles[j].className == "flex-in") {
        articles[j].className = "flex-out";
      } else {
        articles[j].className = "flex-in";
      }
    } else if(articles[j].className == "flex-out") {
      articles[j].className = "flex-in";
    }
  }
}

for(i=0;i<=(articles.length-1);i++) {
  articles[i].onclick = function() {
    var articleId = this.id;
    toggleFlex(articleId);
  }
}	

但有一个问题,简而言之,真差劲!它似乎并没有很好的在不同浏览器上工作,动画也差。于是我决定重新思考这个问题。

最后的尝试:transitions、javascript和向后兼容

我思考了很多,并决定尝试使用javascript切换技术以入transition。这样一来问题就变更简单、更有效。你只是把你的css样式像以前一样转换,但不包括悬浮状态。相反,你可以通过给元素添加类,通过js点击,从而触发转换。除此之外,还有:

  1. 使盒子填满整个屏幕的宽度和高度;
  2. 添加回去标题,固定显示,以提搞可访性
  3. 使用Modernizr库处理老式浏览器的兼容性;
  4. 使用键盘可访问

案例最终效果。

第一件事情,我把Modernizr加入页面,并且添加了一些google fonts(如何添加,请参考《Google Font的运用》一文)。

<script src="modernizr-flex.js"></script>
<link href='http://fonts.googleapis.com/css?family=Viga|Bowlby+One|Montaga' rel='stylesheet' type='text/css'>	

接下来,给<html>、<body>和<article>运用样式:

html {
  font-size: 10px;
  font-family: 'Montaga', serif;
  width: 100%;
  height: 100%;
  margin: 0;  
}

body {
  width: 100%;
  height: inherit;
  margin: 0;
  
  display: -ms-flexbox;
  -ms-box-orient: horizontal;
  
  display: -webkit-flex;
  display: -moz-flex;
  display: flex;
}

.no-flexbox body {
  display: -webkit-box;
  display: -moz-box;
  display: -ms-flexbox;
}

article {  
  -webkit-flex: 1 0;
  -moz-flex: 1 0;
  -ms-flex: 1 0;
  flex: 1 0;
  
  margin: 0.5rem;
  padding: 1rem;
  border-radius: 1rem;
  color: rgba(0,0,0,0);
  
  background: -webkit-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: -moz-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: -ms-linear-gradient(top left,rgba(0,0,0,0),rgba(0,0,0,0.4));
  background: linear-gradient(to bottom right,rgba(0,0,0,0),rgba(0,0,0,0.4));
  
  -webkit-transition: 1s all;
  -moz-transition: 1s all;
  -ms-transition: 1s all;
  -o-transition: 1s all;
  transition: 1s all;
  
  overflow: hidden;
  position: relative;
  
  padding-top: 5rem;
}

.no-flexbox article {
  -webkit-box-flex: 1;
  -moz-box-flex: 1;
  width: 200px;
}	

在这里定义了transition,但这次不是通过"article:hover"来触发。此外,我已经使用类名“.no-flexbox”来处理浏览器不支持flxbox的新语法,这个类名是通过Modernizr库生成。而且在ie添加了特性的属性“-ms-flexbox”、“-ms-box-orient:horizontal”等。这样一来,这个例子可以在IE10、Safari、firefox下都得到友好的运行。

注:阅读我之前写的《Intelligent fallbacks for flexbox 》一文,这篇文章介绍了flexbox更多详细信息以及当前状态、flexbox浏览器支持遗留语法在跨浏览器的解决方法。

还要注意<html>的宽度和高度都是100%以及margin为0,并且<body>继承了父元素的高度和margin:0。这迫使内容填允整个视窗,这样无论高度和宽度,flexbox都能很好的伸缩。

接下来我们来看一下javascript:

var articles = document.getElementsByTagName('article');
  
  var toggleFlex = function(articleId) {
    for(j=0;j<=(articles.length-1);j++) {
      if(articles[j].id == articleId) {
        articles[j].focused = true;
        if(articles[j].className == "") {
          if(Modernizr.flexbox) {
            articles[j].className = "flex-out";
            articles[j].style.cssText = "-webkit-flex: 1 500px;-moz-flex: 1 500px;-ms-flex: 1 500px;flex: 1 500px;color: rgba(0,0,0,1);"
          } else if(Modernizr.flexboxlegacy) {
            articles[j].className = "flex-out";
            articles[j].style.cssText = "width: 500px;color: rgba(0,0,0,1);"
          }
        } 
      } else if(articles[j].className == "flex-out") {
          articles[j].className = "";
          articles[j].style.cssText = ""
      }
    }
  }
  
  for(i=0;i<=(articles.length-1);i++) {
    articles[i].onfocus = function() {
      var articleId = this.id;
      toggleFlex(articleId);
    }
  }	

这个和前面的很相似,我遍历所有的<article>,但这次不同的是我使用“onfocus”事件而没有使用“onclick”事件。这意味着,这个添加了解盘可访问性。通过添加tabindex="0",<article>就可以得到焦点。不过这样产生一个新的问题,现在你不能关闭展开的<article>,你只能通过点击不同的<article>来进行关闭。为了这个问题,我想了一天。

在toggleFlex()函数中,我遍历了所有的<article>,通过检查它的ID值来检查哪个是被点击过的。一个是点过打开,里面的文字可见的,给它一个不同的flex-basis值(或一个不同的宽度)和文本不透明,并且添加一个类名用来当作开关进行设置。

if(Modernizr.flexbox) {
  articles[j].className = "flex-out";
  articles[j].style.cssText = "-webkit-flex: 1 500px;-moz-flex: 1 500px;-ms-flex: 1 500px;flex: 1 500px;color: rgba(0,0,0,1);"
} else if(Modernizr.flexboxlegacy) {
  articles[j].className = "flex-out";
  articles[j].style.cssText = "width: 500px;color: rgba(0,0,0,1);"
}	

其它的<article>不会受到影响,除非他自己是展开状态,在这种情况下,它们再度通过添加和删除类名来进行伸缩控制。

else if(articles[j].className == "flex-out") {
  articles[j].className = "";
  articles[j].style.cssText = ""
}	

最后一个需要处理的就是标题。我把标题<h2>设置比较小(字段长度)或者隐藏他们,所以他们不会陷入前面提到的布局问题,但屏幕阅读器仍然可以读得到:

h2 {
  line-height: 0;
  font-size: 0;
  visibility: hidden;
}	

使用伪类和绝对定位,来显示标题,增加视觉效果,这样处理可以解决标题影响flexbox布局在小窗口下的问题:

body {
  counter-reset: chapter 0;
}

article { 
  counter-increment: chapter;
}

article:before {
  font-family: 'Bowlby One', cursive;
  position: absolute;
  top: 13px;
  left: 10px;
  content: "Chapter " counter(chapter);
  font-size: 2rem;
  font-size: 1.5vw;
  color: rgba(0,0,0,0.5);
}	

总结

这篇文章一直是我想写的,希望和大家一起探讨这个有趣的技术,更希望这些内容能给大家带来帮助。值得注意的是,你可以通过浮动和得到焦点,达到类似的效果。然而这样做是互板的,它依赖于知道确切的盒子个数(<article>),同时也出会通过设置百分比来做自适应。但与flexbox方法相比,flexbox会让你不需要管理有多少个盒子数,而且不需要担心怎么准确的计算,都能很好的运行。

译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!

如需转载烦请注明出处:

英文原文:http://dev.opera.com/articles/view/animating-flexboxes-the-lowdown

中文译文:http://www.w3cplus.com/css3/animating-flexboxes-the-lowdown.html

返回顶部