【第1114期】打造高性能剪切动画

2023-10-17 16:10

本文主要是介绍【第1114期】打造高性能剪切动画,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前言

今天开始大部分地区都降温了,而且降的幅度很大。今日早读文章由布谷前端@程松翻译分享。

正文从这开始~

在谷歌的产品中我们经常会看到各种各样的交互动画,对于前端开发来说实现动画效果也是经常遇到且头疼的事情,原作者 Paul Lewis 是谷歌的一名工程师,在这篇文章中我们将探讨如何实现如下的交互设计

上面的交互效果是将一个菜单分为收起状态和展开状态,在收起状态下只展现收起状态下的一部分,所以我们可以看成收起状态是把展开态的一部分给剪切掉了,这也是为什么原文中作者使用 animating clips 来描述这种交互形式。

方案1:通过动画修改容器的宽高

我们定义好菜单收起和展开两种状态下的 width 和 height 属性,并且给元素加上 transition 过渡,通过切换元素的 class 实现动画效果。具体的实现代码如下:

.menu {overflow: hidden;width: 350px;height: 600px;transition: width 600ms ease-out, height 600ms ease-out;}.menu--collapsed {width: 200px;height: 60px;}

这个方法简单粗暴见效快,但对于老司机来说这并不是一个满意的方案,因为在修改元素的样式属性时浏览器会重新渲染页面,对 width 和 height 属性而言,每一次修改之后页面都需要进行重排,这个渲染过程开销相对而言比较大,所以动画很难达到60fps。

方案2:使用 CSS clip 或 clip-path 属性

除了上面说的修改宽和高之外我们还可以使用 clip 属性(已经废弃)来实现。当然,因为 clip 已经弃用了,我们应该使用 clip-path 属性搞定,但 clip-path 目前的浏览器兼容性并不好,所以目前使用clip 或 clip-path 也不是一个完美的解决方案。

.menu {position: absolute;clip: rect(0px 112px 175px 0px);transition: clip 600ms ease-out;}.menu--collapsed {clip: rect(0px 70px 34px 0px);}

这个方案性能上比方案1要好,但仍旧会在每一帧触发重绘。并且 clip 属性需要元素是 absolute 或 fixed 定位,这样可能会让 CSS 代码略麻烦。

方案3:CSS Animating 和 scale

方案1修改的是元素的大小,方案2是裁剪元素的可视区域,现在我们换一种思路。

首先从效果上看我们可以认为菜单的收起态是展开态缩放而来的,也就是说我们将展开态高度所放到原来的 1/5 就是收起态的高度。我们对元素做 transform: scale(0.2, x)(这里没考虑宽度的缩放比例) 操作,效果如下

现在菜单的高度缩小为原来的 1/5,但是问题就是菜单的内容也被相应的缩小了,要解决这个问题很简单,我们直接把菜单的内容放大 5 倍即可。

所以方案三其实是在容器上设置一个缩放,然后在内容上设置一个反向缩放(counter-scale),这样就可以保证在容器收起或展开的时候菜单里的内容不会被压缩或者放大。这个方法逻辑上稍显麻烦,但是修改 transfrom 属性不会触发重排或者重绘,并且可以利用 GPU 进行运算,因此可以达到更高的性能从而达到更高的帧率。

下面详细说一下实现步骤:

第一步:计算开始和结束状态

首先要得到菜单收起和展开状态下的大小,由此来计算出我们的缩放比例。当然,我们并不能一次取到菜单收起和展开状态下的大小,因此需要先切换 class 来改变元素的状态,然后获取到对应状态下的大小(或者将菜单第一个子元素的大小看做收起态的大小,下面代码就是这样做的)。

需要注意的是当我们执行 getBoundingClientRect()(或者 offsetWidth 和 offsetHeight)时,如果页面当前样式有变化,浏览器会强制重排。

function calculateCollapsedScale () {// The menu title can act as the marker for the collapsed state.const collapsed = menuTitle.getBoundingClientRect();// Whereas the menu as a whole (title plus items) can act as// a proxy for the expanded state.const expanded = menu.getBoundingClientRect();return {x: collapsed.width / expanded.width,y: collapsed.height / expanded.height  }}

菜单默认状态下是展开的,通过上面的代码可以得到菜单收起态和展开态的缩放比例,然后使用 JavaScript 修改元素的 tansform 属性,这样就可以实现展开和收起动画。

.menu {will-change: transform;transition: 200ms linear;}
var { x, y } = calculateCollapsedScale();var invX = 1 / x;var invY = 1 / y;function toggle() {if (menu.classList.contains('expanded')) {collapse();return;}expand();}function collapse() {  menu.classList.remove('expanded');  menu.style.transform = `scale(${x}, ${y})`;  menuContents.style.transform = `scale(${invX}, ${invY})`;}function expand() {  menu.classList.add('expanded');  menu.style.transform = `scale(1, 1)`;  menuContents.style.transform = `scale(1, 1)`;}menuBtn.addEventListener('click', toggle);


从效果看可以发现虽然菜单的收起态和展开态没有问题,但是在变换过程中内容看起来变形了。因为我们是通过容器的缩放和内容的反向缩放(counter-scale)来实现动画的,要保证内容在动画过程中不变形,就要保证容器的缩放比例和内容的缩放比例在动画过程中始终保持相乘等于1。我们用高度举例,收起态的高度是展开态的 1/5(0.2),所以收起态下内容的高度是放大了5倍,那么展开动画就是容器高度缩放比例从 0.2 -> 1,内容高度缩放比例从 5 -> 1。假设缓动函数是线性的,我们把过程分成五个阶段,用表格表示:

容器高度缩放比例内容高度缩放比例0.250.440.630.8211
所以我们可以看到,在整个动画过程中,容器高度缩放比例 * 内容高度缩放比例 > 1,这也就是解释了为什么我们看到的动画中内容变形了。

第二步:构造CSS动画

针对上面的问题,我们可以使用 CSS 动画解决,因为 CSS 动画是基于关键帧,因此我们可以将动画过程分成 100 个关键帧,计算出每一个关键帧下容器和内容的缩放比例,并保证在这 100 个关键帧中这两个比例相乘始终等于 1,接着将这 100 个关键帧拼接成 CSS 动画并插入到页面中给元素调用。这样我们就可以保证在动画过程中不会出现变形,并且一开始就完成所有的计算量,避免了在 JavaScript 中动态计算,从而避免了由于 JavaScript 阻塞导致的动画卡顿。

将 CSS 动画插入到页面中会导致浏览器重新渲染页面样式,但这个过程只会在最开始执行一次,所以影响并不大。下面是通过 JavaScript 生成 CSS 动画的代码

function createKeyframeAnimation () {// Figure out the size of the element when collapsed.let {x, y} = calculateCollapsedScale();let animation = '';let inverseAnimation = '';for (let step = 0; step <= 100; step++) {// Remap the step value to an eased one.let easedStep = ease(step / 100);// Calculate the scale of the element.const xScale = x + (1 - x) * easedStep;const yScale = y + (1 - y) * easedStep;animation += `${step}% {transform: scale(${xScale}, ${yScale});}`;// And now the inverse for the contents.const invXScale = 1 / xScale;const invYScale = 1 / yScale;inverseAnimation += `${step}% {transform: scale(${invXScale}, ${invYScale});}`;}return `@keyframes menuAnimation {${animation}}@keyframes menuContentsAnimation {${inverseAnimation}}`;}

代码中的 ease() 表示缓动函数,在实际应用中,我们可以如下一样自己定义一个,或者使用 Tween.js 这样的项目。

function ease (v, pow=4) {return 1 - Math.pow(1 - v, pow);}

震惊!谷歌竟然能看函数曲线!So Google It!

第三步:执行 CSS 动画

通过前两步我们已经生成了 CSS 动画并且插入到了页面中,接下来我们通过切换元素的 class 来触发动画。

.menu--expanded {animation-name: menuAnimation;animation-duration: 0.2s;animation-timing-function: linear;}.menu__contents--expanded {animation-name: menuContentsAnimation;animation-duration: 0.2s;animation-timing-function: linear;}

结合上面的 CSS 代码,使用 JavaScript 切换元素的 class 就可以触发元素执行动画。这里要注意在 JavaScript 中我们已经引入了缓动函数,所以在 CSS 代码中需要将 animation-timing-function 属性设置为 linear,如果设置成其他值的就相当于在我们原先的缓动基础上再叠加一次缓动效果。

现在我们搞定了展开动画,对于收起动画可以将展开动画逆向执行,这个方法看起来是没有问题的,但是因为是完全逆向执行的,所以假设我们展开动画缓动函数是 ease-out ,那么收起动画看起来就是 ease-in,所以视觉效果上会有一些区别。所以更好的方案是在 JavaScript 中生成两套 CSS 动画,分别是展开动画和收起动画,这样就可以保证两个动画的关键帧是遵循同样的缓动函数。

const xScale = 1 + (x - 1) * easedStep;const yScale = 1 + (y - 1) * easedStep;

再进一步:圆形菜单

现在可以用同样的原理实现一个圆形的菜单动画,其实代码层面基本是一致的。只是圆形这种情况下我们需要设置一个 border-raidu: 50% 来实现圆形,然后用一个设置了 overflow: hidden 的元素将菜单包裹起来,这样就可以在菜单展开后看起来是一个矩形。

当然,这里还涉及一些其他样式上的修改,因为在圆形彩蛋动画中变换中心并不是左上角或者中心,而且点击后加号按钮会消失,所以还需要注意这些细节。在低 DPI 的屏幕上 Chrome 浏览器会出现文字模糊的 bug,具体的 bug 说明在这里。圆形菜单动画代码戳这里;

总结

现在我们已经实现了一种高性能动画方案,原理是将容器和内容同时缩放并且保证两个缩放比例相乘等于 1。具体的过程是先通过 JavaScript 计算出 100 个关键帧并拼接成 CSS 动画,然后触发元素执行 CSS 动画即可。

如果浏览器支持 Web Animation 的话可以直接调用 Web Animation API 执行动画,但问题是 Web Animation 的兼容性不太好,所以使用的话需要做如下兼容处理。

if ('animate' in HTMLElement.prototype) {// Animate with Web Animations.} else {// Fall back to generated CSS Animations or JS.}

想要看具体的实现代码,移步 UI Element Samples Github 仓库。

相关文章

  • infinite scroll

  • performant parallaxing

最后,为你推荐:

【第1001期】构建高性能展开&收缩动画


【第892期】功能性动画如何提升用户体验


关于本文

译者:@程松

译文:https://zhuanlan.zhihu.com/p/26519940

作者:@Paul Lewis、@Stephen McGruer

原文:https://developers.google.com/web/updates/2017/03/performant-expand-and-collapse


【第1107期】iPhone X 适配 手Q H5页面通用解决方案

【第1106期】Element 中的键盘可访问性

这篇关于【第1114期】打造高性能剪切动画的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/226489

相关文章

【前端学习】AntV G6-08 深入图形与图形分组、自定义节点、节点动画(下)

【课程链接】 AntV G6:深入图形与图形分组、自定义节点、节点动画(下)_哔哩哔哩_bilibili 本章十吾老师讲解了一个复杂的自定义节点中,应该怎样去计算和绘制图形,如何给一个图形制作不间断的动画,以及在鼠标事件之后产生动画。(有点难,需要好好理解) <!DOCTYPE html><html><head><meta charset="UTF-8"><title>06

MySQL高性能优化规范

前言:      笔者最近上班途中突然想丰富下自己的数据库优化技能。于是在查阅了多篇文章后,总结出了这篇! 数据库命令规范 所有数据库对象名称必须使用小写字母并用下划线分割 所有数据库对象名称禁止使用mysql保留关键字(如果表名中包含关键字查询时,需要将其用单引号括起来) 数据库对象的命名要能做到见名识意,并且最后不要超过32个字符 临时库表必须以tmp_为前缀并以日期为后缀,备份

Flutter 进阶:绘制加载动画

绘制加载动画:由小圆组成的大圆 1. 定义 LoadingScreen 类2. 实现 _LoadingScreenState 类3. 定义 LoadingPainter 类4. 总结 实现加载动画 我们需要定义两个类:LoadingScreen 和 LoadingPainter。LoadingScreen 负责控制动画的状态,而 LoadingPainter 则负责绘制动画。

基于 YOLOv5 的积水检测系统:打造高效智能的智慧城市应用

在城市发展中,积水问题日益严重,特别是在大雨过后,积水往往会影响交通甚至威胁人们的安全。通过现代计算机视觉技术,我们能够智能化地检测和识别积水区域,减少潜在危险。本文将介绍如何使用 YOLOv5 和 PyQt5 搭建一个积水检测系统,结合深度学习和直观的图形界面,为用户提供高效的解决方案。 源码地址: PyQt5+YoloV5 实现积水检测系统 预览: 项目背景

pip-tools:打造可重复、可控的 Python 开发环境,解决依赖关系,让代码更稳定

在 Python 开发中,管理依赖关系是一项繁琐且容易出错的任务。手动更新依赖版本、处理冲突、确保一致性等等,都可能让开发者感到头疼。而 pip-tools 为开发者提供了一套稳定可靠的解决方案。 什么是 pip-tools? pip-tools 是一组命令行工具,旨在简化 Python 依赖关系的管理,确保项目环境的稳定性和可重复性。它主要包含两个核心工具:pip-compile 和 pip

用Unity2D制作一个人物,实现移动、跳起、人物静止和动起来时的动画:中(人物移动、跳起、静止动作)

上回我们学到创建一个地形和一个人物,今天我们实现一下人物实现移动和跳起,依次点击,我们准备创建一个C#文件 创建好我们点击进去,就会跳转到我们的Vision Studio,然后输入这些代码 using UnityEngine;public class Move : MonoBehaviour // 定义一个名为Move的类,继承自MonoBehaviour{private Rigidbo

构建高性能WEB之HTTP首部优化

0x00 前言 在讨论浏览器优化之前,首先我们先分析下从客户端发起一个HTTP请求到用户接收到响应之间,都发生了什么?知己知彼,才能百战不殆。这也是作为一个WEB开发者,为什么一定要深入学习TCP/IP等网络知识。 0x01 到底发生什么了? 当用户发起一个HTTP请求时,首先客户端将与服务端之间建立TCP连接,成功建立连接后,服务端将对请求进行处理,并对客户端做出响应,响应内容一般包括响应

Nginx高性能分析

Nginx 是一个免费的,开源的,高性能的 HTTP 服务器和反向代理,以及 IMAP / POP3 代理服务器。Nginx 以其高性能,稳定性,丰富的功能,简单的配置和低资源消耗而闻名。本文从底层原理分析 Nginx 为什么这么快! Nginx 的进程模型 Nginx 服务器,正常运行过程中: 多进程:一个 Master 进程、多个 Worker 进程。Master 进程:管理 Work

云原生之高性能web服务器学习(持续更新中)

高性能web服务器 1 Web服务器的基础介绍1.1 Web服务介绍1.1.1 Apache介绍1.1.2 Nginx-高性能的 Web 服务端 2 Nginx架构与安装2.1 Nginx概述2.1.1 Nginx 功能介绍2.1.2 基础特性2.1.3 Web 服务相关的功能 2.2 Nginx 架构和进程2.2.1 架构2.2.2 Ngnix进程结构 2.3 Nginx 模块介绍2.4

如何打造个性化大学生线上聊天交友系统?Java SpringBoot Vue教程,2025最新设计思路

✍✍计算机编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java实战 | SpringBoot/SSM Python实战项目 | Django 微信小程序/安卓实战项目 大数据实战项目 ⚡⚡文末获取源码 文章目录