CSS3 的过渡属性, 给 web 应用带来了简单优雅的动画, 但是比起初次相见, 他 (transition) 有许多细则.
在这片文章中, 我将会专研 CSS3 的过渡 (transition) 中更加复杂的部分, 从链式和事件到硬件加速和动画函数.
让浏览器控制动画序列, 通过改变帧率, 减少绘画和减少 GPU 的工作, 能够优化性能和效率.
应用 transition
一个最简单使用 transition 的方法就是和 CSS 伪元素一起用, 比如: hover. 注意我们在指定属性名字, transition 的时长, 以及默计时函数, linear.
- .element {
- height: 100px;
- transition: height 2s linear;
- }
- .element:hover {
- height: 200px;
- }
当: hover 伪元素被激活的时候, 这高度会动态地在两秒内从 100px 过度到 200px.
duration 是唯一在 transition 缩写中需要的项目. 浏览器默认的定时方法是 ease, 以及一个 all 的属性, 除非他们已经提供了.
当谈论到激活 transition, 我们不希望被限制于使用伪元素 -- 很显然这不灵活. 解决这个的方法就是用程序添加和删除 class
专门建立的学习 Q-q-u-n: 731771211, 分享学习方法和需要注意的小细节, 不停更新最新的教程和学习技巧
- (从零基础开始到前端项目实战教程, 学习工具, 全栈开发学习路线以及规划)
- /* CSS */
- .element {
- opacity: 0.0;
- transform: scale(0.95) translate3d(0,100%,0);
- transition: transform 400ms ease, opacity 400ms ease;
- }
- .element.active {
- opacity: 1.0;
- transform: scale(1.0) translate3d(0,0,0);
- }
- .element.inactive {
- opacity: 0.0;
- transform: scale(1) translate3d(0,0,0);
- }
- // JS with jQuery
- var active = function(){
- $('.element').removeClass('inactive').addClass('active');
- };
- var inactive = function(){
- $('.element').removeClass('active').addClass('inactive');
- };
以上的列子, 我们用了 2 个不同的过渡(transition), 当激活的时候, 元素向上滑动, 当无效化之后, 淡出. 所有的 JavaScript 所做的事就是切换 active 和 inactive 这两个 class.
过渡渐变
不是所有的 CSS 属性都能过渡, 最基本的规则是你只能过渡绝对值. 比如, 你不能让 height 从 0px 过渡到 auto, 浏览器不能计算中间过度值, 因此属性变化是瞬间的.
同时, 有一些很好的解决方法. 第一个方法包括添加透明度到渐变, 然后过渡到背景色. 比如:
- .panel {
- background-color: #000;
- background-image: linear-gradient(rgba(255, 255, 0, 0.4), #FAFAFA);
- transition: background-color 400ms ease;
- }
- .panel:hover {
- background-color: #DDD;
- }
如果渐变是持续的, 你可以过渡 background-position, 就像这里写的, 否则, 你的最后手段是创建两个元素, 一个放在另一个之上, 然后过渡你的透明度.
- .element {
- width: 100px;
- height: 100px;
- position: relative;
- background: linear-gradient(#C7D3DC,#5B798E);
- }
- .element .inner {
- content: '';
- position: absolute;
- left: 0; top: 0; right: 0; bottom: 0;
- background: linear-gradient(#DDD, #FAFAFA);
- opacity: 0;
- transition: opacity 1s linear;
- }
- .element:hover .inner {
- opacity: 1;
- }
后者方法的需要注意的是, 这需要额外的标记, 并且在内部的 div 能够捕捉到指针事件. 伪元素, 类似: before 和: after 可以是过度理想的使用案例.
硬件加速
过渡某个属性, 比如 left 和 margin 会导致浏览器每帧都会重新计算样式. 这消耗相当昂贵, 并且可能会导致不必要的重绘, 特别是如果你在屏幕上有很多元素. 这在低性能设备上显得特别明显, 比如手机.
这个解决方案是使用 CSS 过渡来减少渲染给 GPU 带来的压力. 简单来说, 这在过渡的时候, 将元素变成了一张图片, 避免任何样式重新计算, 这极大程度上增加了性能. 一个简单强迫浏览器用硬件渲染一个元素的方法是, 设置转型的 Z 轴, 这个你可以用 translate3d:
transform: translate3d(0,0,0);
不过这不是根治性能的方法, 并且会带来许多本身的问题. 只有当需要的时候, 你才应该用硬件加速, 并且完全不需要在每个元素上都使用它.
比如, 硬件加速会导致微妙的字体问题, 比如一个字体出现的时候失去了加粗效果. 这是因为一个 bug, 当元素开启硬件加速的时候, 不支持子像素抗锯齿. 你可以看到在两个渲染模式下的一个清晰的差别.
此外, 不同的浏览器用不同的硬件加速的库, 这可能会造成跨浏览器问题. 比如, 当 Chrome 和 Safari 都是 WebKit 内核的, Chrome 使用 Skia 来做图形渲染, 然而同时 Safari 用的 CoreGraphics. 这两个库之间的差别是细微的, 但是确是存在.
你可以用 Chrome 的开发者工具概览页面, 显示所有的重绘. 外加你可以在开发者工具选项中选择显示三角形, 甚至可以通过 about:flags 打开复合渲染层边界, 来看哪个层是作用在 GPU 上的. 关键是批量在 DOM 刷新下, 减少绘画, 并且从 GPU 上减少最多的的压力.
如果你在浏览器之间因为硬件加速而有了显示问题, 比如闪烁或者颤动, 确保你没有用 transform3d()的 CSS 属性在元素上.
剪裁
为了充分利用 GPU 渲染, 你需要避免使用 CSS 样式变换而不是像 width 这种重新计算样式的属性. 若果你确实需要给元素的宽度做动画? 解决方案就是剪裁.
在以下的例子中个, 你可以看到一个搜索框和 2 个过度状态. 第二个扩展状态被一个剪裁的元素给隐藏了.
过渡这个扩展的宽度, 我们所需要做的就是转变 X 轴到左边. 这边的关键是我们用 translate3d 而不是改变元素的宽度.
- .clipped {
- overflow: hidden;
- position: relative;
- }
- .clipped .clip {
- right: 0px;
- width: 45px;
- height: 45px;
- background: url(/images/clip.PNG) no-repeat
- }
- input:focus {
- -webkit-transform: translate3d(-50px, 0, 0);
- }
确保我们不会在每一帧重新计算元素的宽度, 过渡会变得顺滑和高性能.
时间函数
到目前为止, 我们用了一些浏览器预定义时间函数 linear, ease, ease-in, ease-out 和 ease-in-out. 对于更多复杂的时间函数来说, 我们要写我们自己的时间函数, 通过定义贝塞尔曲线的 4 个关键点.
transition: -webkit-transform 1s cubic-bezier(.17,.67,.69,1.33);
规划过渡
在 CSS 中写过渡非常好, 但是有时候你需要更多控制, 特别是谈到链式过渡的时候. 幸运的是我们不仅能从 JavaScript 中调用过渡, 也能定义他们.
CSS 过渡有一个魔法般的 all 属性, 这确保了任何属性改变都是过渡的. 让我们看看如何实践他们
- var defaults = {
- duration: 400,
- easing: ''
- };
- $.fn.transition = function (properties, options) {
- options = $.extend({}, defaults, options);
- properties['webkitTransition'] = 'all' + options.duration + 'ms' + options.easing;
- $(this).CSS(properties);
- };
现在我们有个 jQuery 函数 $.fn.transition, 我们可以用它来编程调用过渡.
$('.element').transition({background: 'red'});
过渡回调
接下来的步奏是链式过渡, 是过渡结束后回调. 你可以在 Webkit 中获得这个状态, 通过监听 webkitTransitionEnd 这个事件.
专门建立的学习 Q-q-u-n: 731771211, 分享学习方法和需要注意的小细节, 不停更新最新的教程和学习技巧
- (从零基础开始到前端项目实战教程, 学习工具, 全栈开发学习路线以及规划)
- var callback = function () {
- // ...
- }
- $(this).one('webkitTransitionEnd', callback)
- $(this).CSS(properties);
记住有时候事件没有绑定, 经常是在那些属性不改变或者一个绘画没有被触发的情况下. 为了确保我们一直有一个回调, 让我们设置一个超时, 这会手动帮我们触发时间.
- $.fn.emulateTransitionEnd = function(duration) {
- var called = false, $el = this;
- $(this).one('webkitTransitionEnd', function() { called = true; });
- var callback = function() { if (!called) $($el).trigger('webkitTransitionEnd'); };
- setTimeout(callback, duration);
- };
我们在设置元素 CSS 之前, 请求 $.fn.emulateTransitionEnd(), 以确保我们过渡之后会有 CSS 回调.
- $(this).one('webkitTransitionEnd', callback);
- $(this).emulateTransitionEnd(options.duration + 50);
- $(this).CSS(properties);
链式过渡
因此, 现在我们能够通过写程引用过渡, 当他们结束之后获得回调, 我们能够开始排序过渡. 我们能够写自己的序列来做这件事, 但是我们用了 jQuery, 我们最好渗透库的现存方法.
jQuery 提供了 2 个关键函数和他的队列和 API 联系,
.fn.dequeue(). 前者加了一个回调到队列中, 然后后者执行下一个队列中的项目.
换句话说, 我们需要设置我们的 CSS 过渡在
.fn.dequeue
- var $el = $(this);
- $el.queue(function(){
- $el.one('webkitTransitionEnd', function(){
- $el.dequeue();
- });
- $el.CSS(properties);
- });
这个例子相对简单, 但是他让我们创建了复杂的链式动画, 并且甚至使用 jQuery 的 delay()函数: 比如:
- $('.element').transition({left: '20px'})
- .delay(200)
- .transition({background: 'red'});
重新绘制
在过渡的时候, 你会需要两组 CSS 属性. 最初的属性是动画应该从哪里开始, 和最后的属性, 过渡应该在哪里结束.
- $('.element').CSS({left: '10px'})
- .transition({left: '20px'});
然而, 你会发现如果你应用了两组属性, 一个立马在另一个之后运行了, 然后浏览器尝试优化属性改变, 无视你的初始属性和阻止过渡. 在场景之后, 浏览器绘制之前, 批处理属性变动, 经常会加速渲染, 经常会有不良反应.
解决方法是在两组属性之间强迫重绘. 一个简单的方法是获取 Dom 元素的 offsetHeight 属性, 就像这样
- $.fn.redraw = function(){
- $(this).each(function(){
- var redraw = this.offsetHeight;
- });
- };
这在素有的浏览器中有有效, 但是我有次很巧合地在 Android 中, 这依然不行. 可供替代的方法有 timeout 或者切换 class 名.
- $('.element').CSS({left: '10px'})
- .redraw()
- .transition({left: '20px'});
未来
过渡 (Transition) 正在活跃地应用, 以及下一个标准看上去很有前景. 这个建议包括了一个新的 JavaScript 的 API, 这个 API 专注于过渡现存的限制, 并给与开发者更多的灵活性.
来源: http://www.jianshu.com/p/2b10baf42515