当前位置: 首页 > Web前端 > CSS

挤压动画的一次尝试

时间:2023-03-31 00:04:12 CSS

前言前不久在codepen上看到一个点击按钮挤压动画的demo。结合上面的svg路径,似乎SVG的路径有点复杂。然后我想能不能用更简单的代码或者想法来恢复这个效果。看了一些资料,脑子里大概有几个想法:方法一:尝试使用clip-path+animation来实现方法二:尝试使用clip-path+SVGclipPath动画方法三:尝试使用transform的matrix()对于矩阵变换+动画尝试方法一:clip-path+animationclip-path属性用于设置裁剪区域,让元素只显示裁剪区域的部分,最重要的是剪辑路径支持动画!但是经过一番尝试,clip-path目前支持的裁剪形状无法满足挤压动画的需要,即凹曲线;当前clip-path支持的形状有:inset():rectangle;圆圈():圆圈;椭圆():椭圆;多边形():多边形;url():引用SVG形状;几何盒子;其实clip-path有一个很强大的shape来源,就是path()方法,可以使用SVGPath语法来构建一个shape,但是clip-path属性中目前很多浏览器不支持这个方法,所以很遗憾;方法二:clip-path+SVGclipPath瞄准是正确的,因为clip-path可以使用url()方法引用SVG图形,所以我们也可以利用SVG的思想来实现挤出需要的形状。毕竟SVG路径语法很强大,而且还支持贝塞尔曲线,几乎可以绘制任何形状;而具体在SVG中,使用clipPath元素声明一个裁剪区域,然后使用url(#name)来引用它;可以看到clip-path属性的名字就是引用的SVGclipPath,基本用法如下:.demo{clip-path:url("#m1");}clipPath元素中定义的shape就是裁剪区域。除了path之外,还可以使用其他SVG中用来定义shape的元素,比如:,等。;不仅如此,还使用了SVG动画语法对shape进行动画处理;但是经过实践发现,path的d属性在进行动画处理时,在引用时并没有预期的插值关键帧过渡的效果,而是直接跳到最后一帧,也就是说clipPath里面的动画对路径没有影响:。demo{clip-path:url("#m1");}同上,使用animate改变元素的d属性在clipPath中无效;不知道是不是用法不对。反正尝试用SVG动画改变贝塞尔曲线实现挤压动画失败了。。。方法三:matrix()/matrix3d()突然想起可以用matrix()/matrix3d()的方法在transform属性中,也就是说,矩阵变换还有一段路要走;于是去网上找了一些类似挤压动画的无畸变变换。没想到找到了一个类似的叫“圆柱投影变换”;原理很简单,就是通过将一个矩形区域投影到圆柱体的外侧或内侧,得到挤压或拉伸的图形:如果投影平面在圆柱体外侧,则可以得到类似挤压效果的凹曲线:但是,显然这一种变换是非线性变换,而matrix()/matrix3d()只能接受常数作为矩阵元素,所以没有办法实现非线性变换!换个思路:思考原理上面尝试的三种方法都失败了。可能是问题太简单了。我想直接插值现有的属性形成动画,而不添加任何额外的计算;实际上,由于clip-path属性中有一个polygan()方法,可以绘制任意形状的多边形,并且支持动画(即关键帧的自动插值)。然而,在图形学中,所有的曲线本质上都是通过曲线的插值绘制线段得到的;也就是说,我们可以通过插值得到一个近似于挤压动画的多边形形状,只要能找出描述挤压曲线的公式即可,实践证明这是可行的,而且最终的代码也不是很复杂并且可控;挤压曲线插值点坐标解如图,以矩形区域左下角为原点,假设挤压曲线为圆弧,挤压曲线与原点的距离bottomedge最高点的高度(峰高)为$a$,圆弧所在圆的半径为$r$,圆弧对应的弦长的一半为$c$,那么我们可以得到:$$\begin{aligned}r^2&=(r-a)^2+c^2\\[1em]\Rightarrowr&=\frac{a^2+c^2}{2a}\end{aligned}$$根据$r$和圆心坐标可以得到圆的轨迹方程:$$(x-c)^2+(y-a+r)^2=r^2$$根据圆的轨迹方程,挤压曲线(圆弧)部分$y$的解:$$y-a+r=\pm\sqrt{r^2-(x-c)^2}\\[1em]\因为y\geqslant0\quad\land\quadr-a>0\\[1em]\因此y-a+r=\开方{r^2-(x-c)^2}\\[1em]\Rightarrowy=\sqrt{r^2-(x-c)^2}+a-r$$由于已知a和c,则可求得$r$;因此,当$x$确定后,就可以得到对应的$y$值了;这样就可以计算出挤压曲线上各点的坐标,进行插值!插值过程在底部边缘等间隔选取$n$个点,根据这些点的$x$坐标和确定的峰高,可以得到相应位置在挤出曲线上的坐标点位置(在其实主要是$y$值,以水平方向的挤压曲线为例),然后将插值得到的挤压曲线上的点连接起来,得到近似挤压曲线的线段。这些线段闭合后,满足挤压动画Squeeze效果的要求;可以设计一个函数,根据参数(如插值点数、峰高等)自动生成符合polygan()方法的路径格式;如下:/***获取弦上某点对应的弧高差*@param{number}x弧对应的弦偏移位置*@param{number}length挤压弧对应的弦长*@param{number}crest挤压弧的峰高*/functiongetSquishOffset(x,length,crest){consthalf=length/2consthalf_2=half*halfconstcrest_2=crest*crestconstr=(half_2+crest_2)/(2*波峰)返回数学。sqrt(r*r-Math.pow(x-half,2))+crest-r}/***根据配置获取对应元素的挤压动画关键帧参数,拼接形式为多边形(polygan)*@member{number}width元素宽度*@member{number}height元素高度*@member{number}crestX水平挤压曲线峰高*@member{number}crestY垂直挤压曲线峰高*@member{number}pointX水平插入点数*@member{number}pointY垂直插入点数*/functiongetSquishPath({width,height,crestX=3,crestY=3,pointX=11,pointY=11}){letfromTop=[]//顶部+右侧letfromBottom=[]//底部+左侧lettoTop=[]lettoBottom=[]constperX=100/(pointX-1)constperY=100/(pointY-1)for(leti=0;i{demo.classList.add('play')//点击播放动画})demo.addEventListener('animationiteration',()=>{demo.classList.remove('play')//动画结束后暂停一次})后来,我承认这个方法有点“硬核”,包括一些数学公式的推导,但实际上用到的知识只有高中数学,而且工艺也不复杂,就是久不用有点生锈了;而且我第一次推导的时候出错了,有点尴尬,不过推导成功还是蛮舒服的,最后代码也不复杂。最重要的是理解了本质问题,并且应用了,还是很有收获的;以上是推导过程的草稿,好久没写数学推导了,挺有意思的;终于写了一个交互demo,效果看起来还算满意,可能是动画参数还有待打磨;这个交互式demo还可以随时调整squish动画的一些参数,然后查看改变后的效果;demo地址为:一个squish动画demo扩展资料:关于圆柱投影变换的思路28.图像畸变-知乎二维投影几何与变换-圆柱投影,图像拼接圆柱投影_yang6464158的专栏-CSDN博客3d-WarpImagetoAppearinCylindricalProjection-StackOverflow:DeeperCSS3animationwithdetailedprinciplederivationandcodeexamplesExploration(MatrixTransformation)-ShortBook相关文档AnimatingwithClip-Path|CSS技巧Flywheellogojavascript-SVG动画路径的d属性-StackOverflowhtml-如何动画SVG元素中定义的剪切路径?-StackOverflow使用JavaScript修改CSS变量