资讯专栏INFORMATION COLUMN

巧妙使用transform实现环形路径平移动画

KevinYan / 3535人阅读

摘要:参考环形路径平移的方案,做一些调整,就可以得到型路径平移的写法这里初始把元素放在了上面那个半圆环的圆心,然后在的关键帧位置切换为下面的半圆环路径。

最近在CSS Secrets一书看到了这样一节:让一个元素沿环形路径平移。这是一个css动画的问题,但却没有看上去那么简单,其关键点是元素是平移的,也就是说,元素自身并不发生旋转,只是稳定地沿着一个环形的路径移动,像这样:

在书中作者Lea Verou已经给出了解答(实际上,可以追溯到作者更早的这篇博文),不过,我认为再补充一点周边细节知识可能会更易于理解。因此,本文整理了一些东西,将尝试更详细地解答这个问题。

从旋转动画开始

最开始看到这个问题的时候,会很容易想到用transform-origin定义圆心的位置,然后用rotate()进行旋转。css代码大概是这样(半径为150px):

@keyframes spin {
    to {
        transform: rotate(1turn);
    }
}

.avatar{
    animation: spin 10s infinite linear;
    transform-origin: 50% 150px;
}

搭配的html很简单:


对应的效果是:

可以看到,这是一个旋转动画,元素在沿着环形路径移动的同时,自身也会围绕圆心发生旋转。因此,这并不是我们想要的平移效果。

但另一方面,元素沿环形路径移动这一点是符合我们的目标的。所以,可以在这个基础上思考如何改进。

利用多元素的变形相消

w3c的The Transform Function Lists里提到:

If a list of is provided, then the net effect is as if each transform function had been specified separately in the order provided.

意思是,当一个元素的transform添加了多个变换函数时,其效果等同于按照这些变换函数的顺序依次分散添加在多层元素中。例如,以下元素:

其变换结果等效于:

这是一条非常有用的规则。现在,假如有一个应用了旋转变换函数的元素是:

显然,这个元素其实是没有旋转的,因为两个旋转变换函数刚好抵消。这时候,我们再用一下前面的规则,就知道它等同于:

也就是说,内层元素可以通过变形来抵消外层的变形效果

现在回到旋转动画,既然元素已经是沿环形路径移动了,我们要做的就是抵消掉元素自身的旋转。参考上面的原理,我们可以增加一个容器元素:

然后为它们搭配不同的动画:

@keyframes spin {
    to { transform: rotate(1turn); }
}
@keyframes spin-reverse {
    from { transform: rotate(1turn); }
}
.avatar {
    animation: spin 10s infinite linear;
    transform-origin: 50% 150px;
}
.avatar > img {
    animation: spin-reverse 10s infinite linear;
}

这段代码把旋转动画搬到了div.avatar这个容器元素上,然后为

对应的css:

@keyframes spin {
    from { transform: 
        translate(50%, 150px) rotate(0turn) translate(-50%, -150px)
        translate(50%, 50%) rotate(1turn) translate(-50%, -50%); }
    to { transform: 
        translate(50%, 150px) rotate(1turn) translate(-50%, -150px)
        translate(50%, 50%) rotate(0turn) translate(-50%, -50%); }
}
.avatar {
    animation: spin 10s infinite linear;
}

上面的代码特意把transform的值分成两行,分别代表原来的两个元素各自的变换函数。到此,这段代码就已经可以让单个元素达成前文的两个元素的效果了。不过,这段代码还比较冗长,可以再做一点简化。

我们很清楚transform的变换函数的顺序很重要,不能随意交换,但相邻的同类变换函数可以考虑合并。

首先,可以找到位于中间的translate(-50%, -150px)translate(50%, 50%)可以合并,得到translateY(-150px) translateY(50%)(百分比和像素值则不能再合并)。

然后,以from的部分为例,注意rotate(0turn)rotate(1turn)分别来自原来的两个元素,它们的角度值是为了互相抵消准备的,因此必须和为360deg1turn = 360deg):其中一个的角度值为x,另一个则为360 - x

也就是说,元素在rotate(0turn)之前(未发生旋转),和rotate(1turn)之后(发生了两次旋转),元素的角度是一致的(合计刚好转了360deg),此时发生的translate()也可以合并。以此找到最前的translate(50%, 150px)和最后的translate(-50%, -50%),它们可以合并,得到translateY(150px) translateY(-50%)

至此,代码变为:

@keyframes spin {
    from { transform: 
        translateY(150px) translateY(-50%) rotate(0turn) 
        translateY(-150px) translateY(50%) rotate(1turn); }
    to { transform: 
        translateY(150px) translateY(-50%) rotate(1turn) 
        translateY(-150px) translateY(50%) rotate(0turn); }
}
.avatar {
    animation: spin 10s infinite linear;
}

代码虽然看起来没怎么变短,但变换函数更细致明确了。最后,注意最开始的两个translateY(),它们在fromto里都是一样的,因此,完全可以在动画之外,一开始就把元素放在那个位置,从而消除这两个translateY()

实际上,这两个translateY()的位移做的事就是把这个元素放到环形路径的圆心。

这样,代码再变为:

@keyframes spin {
    from { transform: 
        rotate(0turn) 
        translateY(-150px) translateY(50%)
        rotate(1turn); }
    to { transform: 
        rotate(1turn) 
        translateY(-150px) translateY(50%) 
        rotate(0turn); }
}
.avatar {
    animation: spin 10s infinite linear;
}

这就是精简后的单元素环形路径平移的解决方案了。代码直观看上去,可能会觉得比较难理解,毕竟它是我们经过前面这样一大段的分析推理得到的。

尽管如此,也有一篇文章介绍了如何直接理解这段环形路径平移的代码,推荐有兴趣的你看看。

一点额外的尝试 螺旋路径平移

在环形平移路径的代码的基础上,改变起点或终点的圆环半径,可以得到螺旋路径:

@keyframes spin {
    from { transform: 
        rotate(0turn) 
        translateY(-150px) translateY(50%)
        rotate(2turn); }
    to { transform: 
        rotate(2turn) 
        translateY(-50px) translateY(50%) 
        rotate(0turn); }
}

对应的效果:

这里为了体现螺旋效果,把圈数增加到了2圈。

S形路径

把两个环形各取一半拼在一起,就可以得到S型路径。参考环形路径平移的方案,做一些调整,就可以得到S型路径平移的写法:

@keyframes spin{
    0%{
        transform: 
            rotate(-90deg) translateX(50px) rotate(90deg);}
    49.9%{
        transform: 
            rotate(-270deg) translateX(50px) rotate(270deg);}
    50.0% {
        transform: 
            translateY(100px) rotate(-90deg) translateX(50px) rotate(90deg);}
    100% {
        transform:
            translateY(100px) rotate(90deg) translateX(50px) rotate(-90deg);}
}

这里初始把元素放在了上面那个半圆环的圆心,然后在50.0%的关键帧位置切换为下面的半圆环路径。由于这个切换过程会让元素小小地停滞一下,并不是我们想要的动画,所以这里用带小数的关键帧位置来尽可能缩短它的时长,使整个动画更平滑。最终效果是:

一点补充

matrix()transform里一个特殊的变换函数,它可以通过矩阵乘法把rotate()translate()等其他变换函数全部合并在一起。但是,matrix()并不能简化本文的动画代码,因为css动画将无法确认如何生成关键帧之间的补间动画,如果关键帧里只有一个合并后的matrix(),css动画只会按照平铺的方式去完成过渡。

以文章最开始的旋转动画为例,rotate(1turn)转换后是matrix(1, 0, 0, 1, 0, 0),但如果直接写:

@keyframes spin {
    to {
        transform: matrix(1, 0, 0, 1, 0, 0);
    }
}

结果就是,什么也不会发生。

结语

只通过一个transform加上一段神秘代码,就可以做这样特别的动画,我觉得是很有意思的。希望本文的这样一番解读,可以帮助你加深对css的transform的理解。

(重新编辑自我的博客,原文地址:http://acgtofe.com/posts/2016...)

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/111698.html

相关文章

  • SegmentFault 技术周刊 Vol.38 - 神奇的 CSS

    摘要:层叠即表示允许以多种方式来描述样式,一个元素可以被渲染呈现出多种样式。可以让属性的变化过程持续一段时间,而不是立即生效。比如,将元素的颜色从白色改为黑色,通常这个改变是立即生效的,使用后,将按一个曲线速率变化。 showImg(https://segmentfault.com/img/bVZwyL?w=900&h=385); CSS 的全称是 Cascading Style Sheet...

    elliott_hu 评论0 收藏0
  • css过度与动画

    摘要:综上,上面的代码的值都应该加上,即逐帧动画在实现一个卡通影片或者一个复杂的进度指示框,或者的标志时这种场景比较适应逐帧动画。这种平滑特性不适用于逐帧动画的实现。 缓动效果 回弹动画效果是比较常见的动画,比如小球的运动、对于尺寸变化和角度变化使用回弹效果可以增强动画的体验。小面介绍一些简单的缓动效果的动画。 弹跳动画的实现 css中所有过渡和动画都是跟一条曲线(缓动曲线)有关的,这条曲线...

    Kross 评论0 收藏0
  • js初级应用之svg实现环形进度条

    摘要:整理一个绘制环形进度条的,需要的同学拿去用即可定义绘图区域在页面的任何位置,添加绘图面板。 showImg(https://segmentfault.com/img/bVrm3V); 整理一个svg绘制环形进度条的demo,需要的同学拿去用即可 定义svg绘图区域 在html页面的任何位置,添加svg绘图面板。定义svg绘图区域大小 绘制一个圆形 cx 和 cy 属性定义圆点的 x ...

    acrazing 评论0 收藏0
  • SVG 立方体内嵌路径拼接

    摘要:第一部分是正方体部分,第二部分是中的路径动画了,第三部分则是交互旋转。然后在立方体元素上添加鼠标点击事件事件,在回调函数中做处理内容。回调函数中先记录下来鼠标点击的位置。 1.前言 我使用了jquery编写交互的旋转,因为初学所以不太承受,还请见谅。样式我是写stylus编码,自动生成css。 2.正文 先放上一张效果图showImg(https://segmentfault.com...

    姘存按 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<