CSS动画技术日益成熟,为开发者提供了更强大的工具。特别是CSS动画,已经成为解决大多数动画用例的基础。然而,有些动画需要更精细的处理。
众所周知,动画应在合成层运行(此处不再赘述,如有兴趣,请参考相关文献)。这意味着动画的变换或不透明度属性不会触发布局或绘制层。而动画高度和宽度等属性则会触发这些层,迫使浏览器重新计算样式,这是需要避免的。
此外,即使是动画变换属性,如果要实现真正的60 FPS动画,可能也需要借助JavaScript,例如使用FLIP技术来实现更流畅的动画!
然而,使用变换属性进行可展开动画的问题在于,缩放函数与动画高度/宽度属性并不完全相同。它会对内容产生倾斜效果,因为所有元素都会被拉伸(向上缩放)或挤压(向下缩放)。
因此,我的常用解决方案(可能仍然是,原因将在后面详细说明)是Brandon Smith文章中的方法3。这种方法仍然对高度进行过渡,但使用JavaScript计算内容大小,并使用requestAnimationFrame强制进行过渡。在OutSystems,我们实际上使用此方法为OutSystems UI手风琴模式构建动画。
使用JavaScript生成关键帧
最近,我偶然发现了Paul Lewis的另一篇精彩文章,详细介绍了一种新的展开和折叠动画解决方案,这促使我撰写本文并推广这种技术。
用他的话说,主要思想是生成动态关键帧,逐步……
[…] 从0到100,并计算元素及其内容所需的缩放值。然后,这些值可以简化为一个字符串,并将其作为样式元素注入页面。
要实现这一点,主要有三个步骤。
步骤1:计算起始和结束状态
我们需要计算两种状态的正确缩放值。这意味着我们对将作为起始状态代理的元素使用getBoundingClientRect()
,并将其除以结束状态的值。它应该类似于:
function calculateStartScale () { const start= startElement.getBoundingClientRect(); const end= endElement.getBoundingClientRect(); return { x: start.width / end.width, y: start.height / end.height }; }
步骤2:生成关键帧
现在,我们需要运行一个for循环,使用所需的帧数作为长度。(为了确保动画流畅,它不应该少于60帧。)然后,在每次迭代中,我们使用缓动函数计算正确的缓动值:
function ease (v, pow=4) { return 1 - Math.pow(1 - v, pow); } let easedStep = ease(i / frame);
使用该值,我们将使用以下数学公式获得元素在当前步骤的缩放比例:
const xScale = x (1 - x) * easedStep; const yScale = y (1 - y) * easedStep;
然后我们将步骤添加到动画字符串中:
animation = `${step}% { transform: scale(${xScale}, ${yScale}); }`;
为了避免内容被拉伸/倾斜,我们应该对其进行反向动画,使用反向值:
const invXScale = 1 / xScale; const invYScale = 1 / yScale; inverseAnimation = `${step}% { transform: scale(${invXScale}, ${invYScale}); }`;
最后,我们可以返回已完成的动画,或直接将它们注入新创建的样式标签中。
步骤3:启用CSS动画
在CSS方面,我们需要在正确的元素上启用动画:
.element--expanded { animation-name: animation; animation-duration: 300ms; animation-timing-function: step-end; } .element-contents--expanded { animation-name: inverseAnimation ; animation-duration: 300ms; animation-timing-function: step-end; }
您可以在Codepen上查看Paul Lewis文章中的菜单示例(由Chris提供)。
构建可展开部分
掌握了这些基本概念后,我想检查一下是否可以将此技术应用于不同的用例,例如可展开部分。
在这种情况下,我们只需要动画高度,特别是在计算缩放比例的函数中。我们从节标题获取Y值,作为折叠状态,并获取整个节来表示展开状态:
_calculateScales () { var collapsed = this._sectionItemTitle.getBoundingClientRect(); var expanded = this._section.getBoundingClientRect(); // create css variable with collapsed height, to apply on the wrapper this._sectionWrapper.style.setProperty('--title-height', collapsed.height 'px'); this._collapsed = { y: collapsed.height / expanded.height } }
由于我们希望展开的部分具有绝对定位(为了避免它在折叠状态下占用空间),我们使用折叠高度为其设置CSS变量,并将其应用于包装器。这将是唯一具有相对定位的元素。
接下来是创建关键帧的函数:_createEaseAnimations()
。这与上面解释的内容差别不大。对于这个用例,我们实际上需要创建四个动画:
- 展开包装器的动画
- 内容的反向展开动画
- 折叠包装器的动画
- 内容的反向折叠动画
我们遵循与之前相同的方法,运行长度为60的for循环(以获得平滑的60 FPS动画),并基于缓动步骤创建关键帧百分比。然后,我们将它推送到最终的动画字符串:
outerAnimation.push(` ${percentage}% { transform: scaleY(${yScale}); }`); innerAnimation.push(` ${percentage}% { transform: scaleY(${invScaleY}); }`);
我们首先创建一个样式标签来保存完成的动画。由于这是作为一个构造函数构建的,为了能够轻松添加多个模式,我们希望所有这些生成的动画都在同一个样式表中。因此,首先,我们验证元素是否存在。如果不存在,我们创建它并添加一个有意义的类名。否则,您最终会为每个可展开的部分获得一个样式表,这不是理想的。
var sectionEase = document.querySelector('.section-animations'); if (!sectionEase) { sectionEase = document.createElement('style'); sectionEase.classList.add('section-animations'); }
说到这一点,您可能已经在想,“嗯,如果我们有多个可展开的部分,它们是否仍然使用相同名称的动画,并且其内容可能具有错误的值?”
你完全正确!因此,为了防止这种情况,我们还生成动态动画名称。很酷,对吧?
当进行querySelectorAll('.section')
查询以向名称添加唯一元素时,我们使用传递给构造函数的索引:
var sectionExpandAnimationName = "sectionExpandAnimation" index; var sectionExpandContentsAnimationName = "sectionExpandContentsAnimation" index;
然后我们使用此名称在当前可展开部分设置CSS变量。由于此变量仅在此范围内,我们只需要在CSS中将动画设置为新的变量,每个模式将获得其各自的animation-name
值。
.section.is--expanded { animation-name: var(--sectionExpandAnimation); } .is--expanded .section-item { animation-name: var(--sectionExpandContentsAnimation); } .section.is--collapsed { animation-name: var(--sectionCollapseAnimation); } .is--collapsed .section-item { animation-name: var(--sectionCollapseContentsAnimation); }
脚本的其余部分与添加事件监听器、切换折叠/展开状态的函数以及一些辅助功能改进有关。
关于HTML和CSS:需要额外的工作才能使可展开功能正常工作。我们需要一个额外的包装器作为不进行动画的相对元素。可展开的子元素具有绝对位置,因此在折叠时不会占用空间。
请记住,由于我们需要进行反向动画,因此我们使其缩放全尺寸以避免内容出现倾斜效果。
.section-item-wrapper { min-height: var(--title-height); position: relative; } .section { animation-duration: 300ms; animation-timing-function: step-end; contain: content; left: 0; position: absolute; top: 0; transform-origin: top left; will-change: transform; } .section-item { animation-duration: 300ms; animation-timing-function: step-end; contain: content; transform-origin: top left; will-change: transform; }
我想强调animation-timing-function
属性的重要性。它应该设置为linear
或step-end
以避免每个关键帧之间的缓动。
will-change
属性——正如您可能知道的那样——将为变换动画启用GPU加速,以获得更流畅的体验。使用contains
属性,值为contents
,将帮助浏览器独立于DOM树的其余部分处理元素,限制其重新计算布局、样式、绘制和大小属性的区域。
我们使用visibility
和opacity
来隐藏内容,并在折叠时阻止屏幕阅读器访问它。
.section-item-content { opacity: 1; transition: opacity 500ms ease; } .is--collapsed .section-item-content { opacity: 0; visibility: hidden; }
最后,我们有了可展开的部分!以下是完整的代码和演示供您查看:
性能检查
每当我们处理动画时,性能都应该铭记于心。因此,让我们使用开发者工具来检查所有这些工作在性能方面是否值得。使用Performance选项卡(我使用的是Chrome DevTools),我们可以分析动画期间的FPS和CPU使用率。
结果非常好!
使用FPS测量工具更详细地检查值,我们可以看到它始终达到60 FPS的标记,即使是滥用使用也是如此。
最终考虑
那么,最终结论是什么?这是否取代了所有其他方法?这是“圣杯”解决方案吗?
在我看来,不是。
但是……这没关系!它只是列表中的另一个解决方案。并且,与任何其他方法一样,应该分析它是否是针对用例的最佳方法。
这种技术肯定有其优点。正如Paul Lewis所说,这确实需要大量工作来准备。但是,另一方面,我们只需要在页面加载时执行一次。在交互过程中,我们仅仅是切换类(在某些情况下,为了辅助功能,还切换属性)。
然而,这给元素的UI带来了一些限制。正如您在可展开部分元素中看到的那样,反向缩放使其更可靠地用于绝对和画布外元素,例如浮动操作或菜单。由于它使用overflow: hidden
,因此难以设置边框样式。
尽管如此,我认为这种方法潜力巨大。请告诉我您的想法!
以上是表演者可扩展动画:即时建立关键框架的详细内容。更多信息请关注PHP中文网其他相关文章!

当他们在2013年去Chrome时,我们失去了歌剧。与Edge今年早些时候也进行了同样的交易。迈克·泰勒(Mike Taylor)称这些变化为“减少

在本周的综述中,Apple进入Web组件,Instagram如何插入脚本以及一些思考的食物,以进行自托管关键资源。


热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

适用于 Eclipse 的 SAP NetWeaver 服务器适配器
将Eclipse与SAP NetWeaver应用服务器集成。

安全考试浏览器
Safe Exam Browser是一个安全的浏览器环境,用于安全地进行在线考试。该软件将任何计算机变成一个安全的工作站。它控制对任何实用工具的访问,并防止学生使用未经授权的资源。

Atom编辑器mac版下载
最流行的的开源编辑器

Dreamweaver CS6
视觉化网页开发工具

Dreamweaver Mac版
视觉化网页开发工具