前言

探究的过程有意思。今日前端早读课文章由FITURE魔镜高级前端工程师@何青松投稿分享。

@何青松,曾任freeCodeCamp成都社区负责人,现任FITURE魔镜高级前端工程师,目前负责公司国际化业务Web开发,拥有丰富的2d图形开发经验,喜欢追求极致的用户体验。

正文从这开始~~

背景

最近因工作需要,要开发两个比较炫酷的动画效果,早期我个人在这方面积累太少,导致实际开发过程走了不少弯路,本文特此总结一下,希望各位同行能吸取经(jia)验(ban)教训少走弯路早日下班。

废话不多说,我们先来看几个苹果官网比较炫酷的几个动画效果,你在此也可以思考一下,该如何实现这些动画效果。

走过的弯路

当我拿到设计同学的设计稿,告知我要实现的第一个动画就类似这种时,我第一反应是想到用一个视频来进行实现,通过控制视频 currentTime 属性来进行视频进度的控制,再监听scroll事件,当他触发时我就将 currentTime 的值进行加减操作,结果这是非常傻的一个操作。因为当用户在触摸板上进行快速向上或向下滚动时,scroll 事件的响应并不是连续触发,而是节流之后的最后几次响应,如下图所示:

对此,一时我竟不知所措,只好一番Google,找到了原来监听 scroll 事件只是第一步,第二步是需要基于scrollTop、scrollHeight、clientHeight 三者进行计算,求出一个滚动系数,基于该计算结果再进行相关动效计算。

const { scrollTop, scrollHeight, clientHeight } = document.documentElement;
const scrolled = scrollTop / (scrollHeight - clientHeight);

套用上面的公式,我满心欢喜的拿着计算出来的 scrolled 值去设置 currentTime,以为能踩点下班时,却发现动画效果是实现了,但是当我快速的滚动时竟然会出现掉帧卡顿跳跃的情况,对于一个追求极致用户体验的我来说,这种情况我是没法忍受的。

奈何自己不够聪明,只有承认自己的无知,被迫向苹果官网妥协。在工位上认真研究苹果官网上的效果是如何实现的,通过认真调试研究后发现,苹果官网竟然是使用几十张甚至百多张图片逐一去加载、再拼接的方式实现的类似效果,当看到请求面板中那一大堆的网络请求时,我的内心是排斥的,网络上那么多性能优化的文章都告诫我们,要尽可能的减少资源加载的请求,图片能合并就合并,代码能压缩就压缩。但苹果官网却玩的这么开放,找了一圈社区发现已经有博主实现了该效果,效果看起来还相当不错-> Apple iPhone SE - Rotation,但我个人实在是看不下去这一堆网络请求,最终还是没采用该方案去实现。

那几日我一直在想有没有一种可能,既能让请求变少,又能不掉帧,还能实现丝滑的效果,如果能看起来有那么点牛逼哄哄的感觉就更好了。通过Google搜索换了各种关键词均找不到相关资料,那几日真的到了怀疑自己能力的地步,每日百思不得其解、朝思夜想,次日夜里在梦中都在思考这个怎么才能实现,我一直不信邪,也不相信苹果官网所有的类似动效,都使用这种方式去实现的,我翻遍苹果官网每一个产品页面,终于在 ipad-mini 的页面找到了想要的答案。

由于线上代码均被压缩又没有源码,只好在苹果官网压缩后的代码文件中大海捞针,找到他的关键代码实现,通过断点调试一行一行的跟进,最终不负有心人,自己成功复刻了苹果的这个实现效果

Ipad mini的实现方式分解

1. 搭建基本的sticky结构

要实现Ipad mini的这个滚动动效,首先需要搭建一个基于position: sticky定位的页面基本结构,在结构中.sticky节点的高度为100vh,并设置overflow: hidden,这里我们需要让sticky节点一直固定在屏幕顶部,不需要让它进行滚动。.content节点中每一个 section 节点都是一块内容区域,他们的高度由自身需要占用多少滚动距离自行设定,我实现的例子中将content节点下的每一个section子节点的高度都和.timeline-wrapper下的.timeline三个节点高度进行了绑定,因为在用户滚动的时候,用户肉眼看到的是.sticky节点下的内容位移变化,但滚动的响应区域是.timeline-wrapper节点,这样即可实现.timeline-wrapper滚动多少距离,.content节点就设置多少偏移量,从而达到交互与肉眼看到的视觉内容进行匹配。

2. 缓存视频帧

有了上述基本协同的结构后,就可以开始在页面加载的时候,去遍历我们要请求的视频资源列表,拿到列表中每一个视频资源地址,调用createVideo工具函数获取video节点对象。

function createVideo(url) {const video = document.createElement("video");video.src = url;video.muted = true;video.playbackRate = 1;video.currentTime = 0;video.setAttribute("muted", "");video.setAttribute("playsinline", "");video.setAttribute("type", "video/webm");video.setAttribute("preload", "none");video.classList.add("video");video.style.display = "none";window.document.body.appendChild(video);return video;
}

获取到视频资源信息后,我们就可以开始进行视频资源帧的缓存操作,在进行帧缓存之前,我们需要大概计算一个视频资源我们需要具体缓存多少帧,例如我例子中拟定的一个视频大概缓存230帧,默认每一个缓存帧的位置都为false状态。

在正式进行缓存之前,我们需要让视频开始进行播放,并立即去执行视频资源缓冲操作,同时监听canplaythrough事件,该事件触发时表示当前已经加载足够的数据来播放视频,直到其结束都不会再进行缓冲内容了。这样我们在该事件触发的时候就可以放心的去轮训该视频资源,我这里设置的是每30ms就去执行一次视频缓存帧的创建操作,再基于当前视频资源创建视频帧,将创建了的视频帧存储到framsStore数组中以供后续使用。

function cacheFrame(videoMetaData) {return new Promise((resolve, reject) => {const { url, frameCount } = videoMetaData;const video = createVideo(url);const framsStore = new Array(frameCount).fill(false);let videoWidth = 0;let videoHeight = 0;let setIn = 0;let framsNumber = 0;video.play();video.addEventListener("loadedmetadata", (res) => {videoWidth = video.videoWidth;videoHeight = video.videoHeight;});video.addEventListener("ended", () => {resolve(framsStore);});video.addEventListener("waiting", (res) => {clearInterval(setIn);});video.addEventListener("error", () => {reject([]);});video.addEventListener("canplaythrough", (res) => {clearInterval(setIn);setIn = setInterval(() => {if (framsNumber >= frameCount) clearInterval(setIn);framsStore[framsNumber] = createFrame(video, videoWidth, videoHeight);framsNumber++;}, fps);});});
}

在进行视频帧缓存时,我们需要将每一次轮训时的视频资源绘制到承载对象上。如果浏览器支持 OffscreenCanvas 则优先使用该API,否则则降级为通过Canvas画布的方式进行承载。

function createFrame(video, videoWidth, videoHeight) {const canvas = window.OffscreenCanvas? new OffscreenCanvas(videoWidth, videoHeight): document.createElement("canvas");const context = canvas.getContext("2d");canvas.width = videoWidth;canvas.height = videoHeight;context.drawImage(video, 0, 0, videoWidth, videoHeight);return canvas;
}
3. 基于滚动系数渲染缓存的视频帧

当缓存完毕视频帧后,则可以监听scroll事件,在滚动触发时基于计算出的系数与缓存的帧总数相乘,则能求出当前滚动的距离与应该绘制的视频帧。

window.addEventListener("scroll", () => {const scrolled =document.documentElement.scrollTop /(document.documentElement.scrollHeight -document.documentElement.clientHeight);frames = frames.filter((item) => item !== false);const frameIndex = parseInt(frames.length * scrolled) + 1;if (frames[frameIndex] !== undefined) {renderFrame(ctx, frames[frameIndex]);}document.querySelector(".content").style.transform = `matrix(1, 0, 0, 1, 0, -${document.documentElement.scrollTop})`;
});function renderFrame(ctx, frame) {ctx.clearRect(0, 0, 1600, 1176);ctx.drawImage(frame, 0, 0);
}

DEMO:https://github.com/Heqingsong/Animate/tree/master/Ipad%20Mini

总结

本文通过对苹果官网Ipad mini滚动动画的实现原理进行了探究,手动实现了该动画的核心原理部分,还有很多细枝末梢的部分未进行一一实现,苹果官网在实现的过程中还考虑到了更多性能和兼容性方面的问题,这些细节的地方都值得细细推敲和学习,感兴趣的可以去苹果官网Ipad Mini产品页详细调试查看。
对比苹果官网已有的2种滚动动画的实现可以发现,当滚动动效需要在第一屏就进行显示的场景下时,更推荐使用多图的方式进行实现,如果你的网页第一屏没有动画,当用户滚动到第一屏以外的区域才进行动画展示时,则通过视频的方式实现会更好。

具体Demo预览,可通过文末阅读原文查看。

关于本文
作者:@何青松
原文:https://juejin.cn/post/7061976278932389918

苹果官网iPad mini滚动动画实现原理探究相关推荐

  1. timeAxis.js--一个简单的时间轴JS框架--仿苹果官网

    代码已经上传到Github,希望各位大佬指教. ReadMe还是以GitHub为准 GitHub地址:https://github.com/royalknight56/timeAxis.js 基本实现 ...

  2. 超强的苹果官网滚动文字特效实现

    每年的苹果新产品发布,其官网都会配套更新相应的单页滚动产品介绍页.其中的动画特效都非常有意思,今年 iPhone 14 Pro 的介绍页不例外. 最近,刚好有朋友问到,其对官网的一段文字特效特别感兴趣 ...

  3. 从苹果官网细看20年设计变迁史

    在过去很长一段时间,苹果都一直以自己流线型.极简风格的工业设计闻名全球.而97年乔布斯带着NeXT团队荣归苹果之后,这家传奇企业重获新生,它的高速发展的这个阶段也和互联网快速普及的20年有着高度的重叠 ...

  4. 换手机的再等等!iPhone SE2还有戏:苹果官网悄然更新AppleCare+服务计划

    近日,苹果的一系列动作着实给力,与国产厂商的提前几个月开始预热不同,苹果喜欢悄悄放大招.无论是iMac.iPad mini.iPad Air还是备受瞩目的新一代AirPods,均是在毫无征兆的情况下悄 ...

  5. 用这个方法,苹果官网居然比二手市场还便宜,最近剁手的注意了

    朋友们,走过路过不要错过!今年苹果返校季的教育优惠活动它终于来了!听闻这是最后一年免费送耳机了,所以说,这可能是最后一次薅羊毛的机会了,大家抓紧时间上车! 今天小编给大家整理了2022苹果教育优惠活动 ...

  6. 苹果官网再降价 没赶上的用户还能退差价

    今日苹果官网众多产品价格小调,例如最新版本iPad Air的起价从3999元降至3896元,iPad mini从2999元起降至2921元起. 此外据媒体报道,iPhone XS系列降价500元,iP ...

  7. 苹果官网是怎么实现的?(一)

    苹果官网是怎么实现的(一)? 苹果的设计师造出的网页总会让你眼前一亮 1. 随着页面滚动显示动画 随着页面的滚动,页面的文字,图片也会随着进行改变,这样可以营造一种动感的效果,给用户的购买欲赋能? 下 ...

  8. iphone降级_无刘海 iPhone 现身苹果官网iOS 13.3 系统验证已关闭

    iOS 13.3 验证关闭 今天凌晨,苹果关闭了 iOS 13.3 和 iPadOS 13.3 版本的系统验证,这意味着用户已无法从 iOS 13.3.1 降回 iOS 13.3 了. 苹果发布新版系 ...

  9. iPhone13真香了?苹果官网被抢崩了,连夜补货!粉色或成爆款..

    17日晚间20:00,苹果新款iPhone 13系列正式开启首轮预购.果粉们直接把官网买崩了,各大平台的首批备货也火速售罄!随后,话题"苹果官网崩了"."iPhone 1 ...

  10. 苹果官网以旧换新价格暴跌;戴威退出 ofo 法人代表及高管;TensorFlow 2.1.0 发布| 极客头条...

    整理 | 屠敏 快来收听极客头条音频版吧,智能播报由标贝科技提供技术支持. 「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注 ...

最新文章

  1. Soil Ecology Letters被ESCI收录
  2. 概念化学习Django
  3. mysql12----explain
  4. 一个强迫症的Git 选择
  5. 【前端学习日记】利用Vue实现跑马灯的效果
  6. MySQL8常见客户端和启动相关参数
  7. JVM总结---各处总结
  8. ArcGIS中国工具(ArcGISCTools)3.2 安装教程(附安装包下载)
  9. Spring Cloud 服务消费者 rest+ribbon (二)
  10. 苏泊尔电磁炉dcl6907_苏泊尔电磁炉c20显示E3故障检修
  11. 19. PHP 表单验证 - 必填字段
  12. Codeforces525E Anya and Cubes(双向搜索)
  13. dd模式和iso模式_ISO的完整形式是什么?
  14. VS2015社区版、企业版、专业版下载官网地址
  15. Excel 序号自动增长,变更
  16. 2022年你应该知道的十大Python库
  17. 计算机行业职业名称英语,行业英语学习
  18. 【认知实习】虚拟现实体验
  19. 光彩夺目的30款太阳光线照射Ps笔刷
  20. 数据库的部分依赖,完全依赖,传递依赖以及三种范式总结

热门文章

  1. 利用Python绘制三维的规则体(3维柱体、立方体和旋转棱柱)
  2. 关于信息安全专业学习的一些看法
  3. Elasticsearch 父子关系
  4. java搜索引擎框架_搜索引擎框架介绍
  5. 【dedecms】DedeCms的搜索引擎优化方法小结
  6. 打开计算机不显示硬盘盘符,电脑硬盘不显示盘符怎么办 移动硬盘不显示盘符的原因...
  7. 1×pbs缓冲液配方_PBS缓冲液的配方
  8. android iphone 开发者选项,手机开发者选项中的妙用,让你的安卓系统流畅如iOS
  9. ROC评分中概念之阳性预测值/阴性预测值计算方法
  10. android室内地图,室内位置-与地图交互-开发指南-Android 室内地图SDK | 高德地图API...