有一天,Harry Roberts有一段有关于他网站上的代码在twitter求教,如果有可能,在某些方面得到改善。Harry Roberts做的是使用keyframes的carousel动画,所以说使用一些数学计算是有可能得到相应改善。
“Why do we have to learn algebra, Miss? We’re never going to use it…” ―Everyone in my maths class bit.ly/UaM2wf
有什么好主意?
据我所见,Harry Roberts在他的主页上使用了一个carousel动画。可以使用CSS,为什么要使用JavaScript呢,你说对吧。所以他使用CSS动画来制作Carousel动画。这听起来似乎是一个很好的想法,但直到你需要计算keyframe时,你就不会这么认为。
下面是有关于Harry Roberts对Carousel动画做的描述。
滚动Carousel(硬编码,有点让人觉得恶心!)和使用一个小动画(这个动画运动是模糊/动画)。整个Carousel动画需要计算过渡和延迟时间(比如100%):
n * x + (n - 1) * y = 100
其中n是指幻灯片的数量,x是动画在静态时的百分比,y是动画在运动时的百分比。
这个动画有5张幻灯片,根据上面描述,可以得到:
5 * x + (5 - 1) * y = 100
如果知道x和n的值,我们就可以计算出y的值:
(100 - (n * x)) / (n - 1) = y
假设x选择的值为17.5(也就是动画中17.5%位置),我们知道n=5,这样可以计算出y=3.125:
(100 - (5 * 17.5)) / (5 -1) = 3.125
帧从17.5%过渡到3.125%等等,直到100%。
如果我们把x设置为15,那么可以计算出y=6.25:
(100 - (5 * 15)) / (5 -1) = 6.25
如果y运动是从"zero-or-below",这也意味着,我们选择了x值太大。
注意,我们还有一个中间点,也就是在中间过渡帧,这个数可以这们得来:
(a * x) + ((a - 1) * y) + (y / 2)
问题是在n个帧哪一个帧才是中间帧。一般情况是3和4之间:
(3 * 17.5) + ((3 - 1) * 3.125)+ (3.125 / 2) = 60.3125
这的确很混乱。
最终结果是:
css;toolbar:false">@keyframes carousel { 0% { transform: translate3d(0, 0, 0); filter: blur(0); } 17.5% { transform: translate3d(0, 0, 0); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 38.125% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 58.75% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 79.375% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5% { transform: translate3d(-80%, 0, 0); filter: blur(0); } 100% { transform: translate3d(-80%, 0, 0); filter: blur(0); } }
清洁CSS动画
之前我考虑过Sass,减少动画代码。从上面的代码块中,我们不难发现,有一些关键帧是相同的。这样我们可以把代码清理清理,让整个动画变得更简单:
@keyframes carousel { 0%, 17.5% { transform: translate3d(0, 0, 0); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translate3d(-20%, 0, 0); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translate3d(-40%, 0, 0); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translate3d(-60%, 0, 0); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translate3d(-80%, 0, 0); filter: blur(0); } }
Sass怎么玩
关键帧通常可以得到优化。因为@keyframes中循环的帧通常很容易产生重复的动画。我们可以试试。
首先,为了保持一致性,我们照常使用Harry的变量名:n,x和y。别忘了它们的含义:
$n是动画中的帧数
$x是动画帧的百分比,逻辑上是小于或等于100% / $n
$y是动画中每一帧的比例
$n: 5;
$x: 17.5%;
$y: (100% - $n * $x) / ($n - 1);
现在我们在@keyframes中做一个循环:
@keyframes carousel {
@for $i from 0 to $n { // 0, 1, 2, 3, 4
//Sass动画
}
}
在循环中,我们使用Harry的公式计算每一对相同的关键帧(比如41.25%和58.75%):
$current-frame: ($i * $x) + ($i * $y);
$next-frame: (($i + 1) * $x) + ($i + $y);
特别声明:小括号完全可以略去,这里只是用它们来让代码保持更干净。
现在,我们使用这些变量来生成关键帧,别忘了在里面插入正确的CSS(有关于更多的Sass内容,可以点击这里):
#{$current-frame, $next-frame} {
transform: translateX($i * -100% / $frames);
filter: blur(0);
}
非常简单,不是吗?运行第一个循环之后,编译出CSS代码:
0%, 17.5% {
transform: translate3d(0%, 0, 0);
filter: blur(0);
}
剩下的就是Harry说的,在中间帧中添加blur()效果。我们可以使用他的公式计算出:
$halfway-frame: $i * ($x / 1%) + ($i - 1) * $y + ($y / 2);
#{$halfway-frame} {
filter: blur(2px);
}
这里出错了。
无效的CSS:我们预期的关键帧,比如10%,可计算出来的是-1.5625%。
正如你所看到,我们最终得到了一个负的关键帧。这在CSS规范中是禁止的,就算在Sass中也认为这是一个错误,所以我们需要确保这种事情不会发生。实际上,只有当$i为0才会发生。因此,有一个简单的方法来阻止这样的事情发生,根据规则输出$i的值:
@if $i > 0 {
#{$halfway-frame} {
filter: blur(2px);
}
}
错误解决了,这是最后的代码:
$n: 5; $x: 17.5%; $y: (100% - $n * $x) / ($n - 1); @keyframes carousel { @for $i from 0 to $n { $current-frame: ($i * $x) + ($i * $y); $next-frame: (($i + 1) * $x) + ($i + $y); #{$current-frame, $next-frame} { transform: translate3d($i * -100% / $frames, 0, 0); } $halfway-frame: $i * ($x / 1%) + ($i - 1) * $y + ($y / 2); @if $i > 0 { #{$halfway-frame} { filter: blur(2px); } } } }
将这一切放在一个mixin中
到目前为止,这一切都好了?Harry的动画代码工作的很好,所以他没有再次计算。如果他想把幻灯片从5张变成4张,或者说希望动画时间更快或更慢,这一切以得重新开始。
目前我们的变量会污染全范围。同样的,如果在其他地方需要Carousel动画,需要重新设置其他变量名,然后将动画内容复制到新的动画中。这绝不是理想的做法。
这就是我们使用mixin的理由。为了让事情更易理解,我们使用实际的变量名来替代这些字母:
$n换成$frames
$x换成$static
$y换成$animating
另外,为了确保它输出不同的动,可以利用mixin多次调用不同的参数。所以我们需要再添加一个参数:动画的名字。
@mixin carousel-animation($frames, $static, $name: 'carousel') { $animating: (100% - $frames * $static) / ($frames - 1); // Moar Sass }
由于mixin可以在不同的地方调用,为了阻止选择器包括它,可以使用@at-root确保动画输出是在根级别:
@mixin carousel-animation($frames, $static, $name: 'carousel') { $animating: (100% - $frames * $static) / ($frames - 1); @at-root { @keyframes #{$name} { // Animation logic here } } }
在实际中,可以这样调用:
@include carousel-animation( $frames: 5, $static: 17.5% );
编译出来的CSS:
@keyframes carousel { 0%, 17.5% { transform: translateX(0%); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translateX(-20%); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translateX(-40%); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translateX(-60%); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translateX(-80%); filter: blur(0); } }
任务完成了。如果我们想在联系页面使用另一个动,可以这样使用:
@include carousel-animation( $name: 'carousel-contact', $frames: 3, $static: 20% );
是不是十分完美。(^_^)。
总结
这想当的完美。而Harry当初的代码容易阅读,但真的不太容易维护。使用Sass的自动化计算和循环特性,可以变得更佳方便。当然,它也使代码变得更为复杂,但这也使得它易于维护和更新。使用起来也非常的简单。
Sass
// ---- // Sass (v3.4.0.rc.1) // Compass (v1.0.0.alpha.20) // ---- /** * Generate the carousel animation * based on the number of frames * and the pourcentage of a frame spent static * * @param {Number} $n - number of frames * @param {Number} $x - percentage of the animation spent static per frame * @param {String} $animation-name ('carousel') - animation name */ @mixin carousel-animation($frames, $static, $animation-name: 'carousel') { // Make `$static` a percentage in case it's unitless @if unitless($static) { $static: percentage($static); } // Compute the percentage of animation spent animating for each frame $animating: (100% - $frames * $static) / ($frames - 1); // Output the animation at root level // to make sure it doesn't crash if called in a selector @at-root { // Create an animation @keyframes #{$animation-name} { // Loop over the frames @for $i from 0 to $frames { // Compute keyframes $current-frame: $i * $static + $i * $animating; $next-frame: ($i + 1) * $static + $i * $animating; $halfway-frame: $i * $static / 1% + ($i - 1) * $animating + $animating / 2; // Output halfway styles for blur // Avoid a negative keyframes by making sure `$i` is at least `1` @if $i > 0 { #{$halfway-frame} { filter: blur(2px); } } // Output styles for each frame #{$current-frame, $next-frame} { transform: translateX($i * -100% / $frames); filter: blur(0); } } } } } // Generate animation @include carousel-animation(5, 17.5%);
CSS
/** * Generate the carousel animation * based on the number of frames * and the pourcentage of a frame spent static * * @param {Number} $n - number of frames * @param {Number} $x - percentage of the animation spent static per frame * @param {String} $animation-name ('carousel') - animation name */ @keyframes carousel { 0%, 17.5% { transform: translateX(0%); filter: blur(0); } 19.0625% { filter: blur(2px); } 20.625%, 38.125% { transform: translateX(-20%); filter: blur(0); } 39.6875% { filter: blur(2px); } 41.25%, 58.75% { transform: translateX(-40%); filter: blur(0); } 60.3125% { filter: blur(2px); } 61.875%, 79.375% { transform: translateX(-60%); filter: blur(0); } 80.9375% { filter: blur(2px); } 82.5%, 100% { transform: translateX(-80%); filter: blur(0); } }