“我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!”
苹果每年一度的发布会,前不久刚刚结束。毫无疑问今年的爱疯,依旧没啥创新没有新意。但是灵动岛设计,可谓是受到了各大媒体的追捧。部分人更是喊着
支持华为,我买苹果
老段子了。
发布会的第二天,就看到部门的妹纸在看新出的AirPods Pro
,还说道:
“这个官网做的真好看,很有科技感!苹果的程序员就是厉害!”
;
??? 我一脸不服气说道:
“这就厉害了? 让我看看,看我不实现给你看。”
。
然后自己仔细看了下,确实很炫酷很有冲击力,大家可以去看一下 AirPods Pro。看起来挺有难度的,不过我可是搞前端的,男人不能在女人面前说不行啊。(一生好强的男人。。。)
走着, 不能在妹妹面前丢人。。。
1. 苹果官网
首先,我们先来看一下,苹果实现的效果。
2. 思考
看到这里,我脑子里已经在思考这是怎么实现的了。首先我想到的是。。。 相信大家都很聪明,和我有这差不多的童年,这都是我们小时候玩过的。废话不多说上图。
看到了吧,火柴人
都玩过吧。我的想法就是创建一个动画,快速连续切换一系列图像。就像翻书一样,让我们的火柴人
可以动起来。
这时,我们就可以根据用户滚动的位置同步图片的每一帧,向上或者向下时,视觉上看起来像是一整套无缝的动画。
3. 分析
然后我们打开官网F12
审查元素,找到这一元素。我们可以看到Apple
用的Canvas
。
这时,我们就可以初步判断了,我们的想法大概是一致的。然后我就切换到了Network
选择img
,哈哈哈! 看到这里时 我就大概已经知道我该怎么做了。
看一下我图片标的红框位置,这里Apple
的UI
切好了一整套流程的每一帧。并且我们可以轻松通过末尾的路径来精准渲染每帧的图像。拿到了现成的图片路径,我们废话不多说直接开始码。
4. 上代码
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="apple" />
</body>
</html>
// 因为我们要向下滚动页面(即内容不超过视口高度),并且Canvas要停留在视口的顶部。
body {
height: 500vh;
background: #000;
}
canvas {
position: fixed;
max-width: 100vw;
max-height: 100vh;
}
基础的代码我们已经写好了,接下来就是我们的js
的部分了。
1. 使用 getContext 获得渲染上下文 拿到canvas的js基础代码
const html = document.documentElement;
const canvas = document.getElementById("apple");
const context = canvas.getContext("2d");
const imgCount = 65; // 图片的总张数
2. 写一个函数,根据用户的滚动位置返回图片路径
const currentImg = index => (
index < 10 // 这里注意拼接的编号 就在这里做处理了
?
`https://www.apple.com.cn/105/media/us/airpods-pro/2022/d2deeb8e-83eb-48ea-9721-f567cf0fffa8/anim/hero/large/000${index}.png`
:
`https://www.apple.com.cn/105/media/us/airpods-pro/2022/d2deeb8e-83eb-48ea-9721-f567cf0fffa8/anim/hero/large/00${index}.png`
)
3. 这里我们绘制我们的第一张图片
4. 我们的第一张图片已经成功渲染到我们的Canvas
了,下面我们将根据用户滚动控制图片的每一帧渲染。
这里我们需要知道:
- 滚动开始结束的地方
- 滚动的多少
- 滚动进度对应的图片
我们用 scroll
的一些相关参数,获取我们需要的一些相关信息进行计算,得到想要的值。
然后通过scroll
事件,获取滚动进度与我们的图片序号进行对应。
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const index = Math.min(
imgCount - 1,
Math.ceil(scrollFraction * imgCount )
);
});
5. 写一个回调函数,可以使其跟随我们的滚动更新渲染匹配的图片
const updateImg = index => {
context.clearRect(0, 0, canvas.width, canvas.height) // 清除上次img的渲染
img.src = currentImg(index);
context.drawImage(img, 0, 0);
}
6. 这里我们使用 requestAnimationFrame 来匹配浏览器的刷新率,可以让我们图片的每一帧平滑的过度。
window.addEventListener('scroll', () => {
const scrollTop = html.scrollTop;
const maxScrollTop = html.scrollHeight - window.innerHeight;
const scrollFraction = scrollTop / maxScrollTop;
const index = Math.min(
imgCount - 1,
Math.ceil(scrollFraction * imgCount )
);
requestAnimationFrame(() => updateImg(index + 1))
});
7. 由于我们滚动的比较快速,需要更新的图片高达六十多张,新图像每次都要从新发起新的网络请求。
这里我们使用预加载,这样我们的每一帧都下载完成,我们的滚动就可以更丝滑了。
(毕竟咱是市级露娜,必须丝滑。狗头)
const preloadImg = () => {
for (let i = 1; i < imgCount ; i++) {
const img = new Image();
img.src = currentImg(i);
}
};
5. 完成 看效果
AirPods Pro 一代
AirPods Pro 二代
不知道为什么在代码片段里会有一些小闪烁,我本地是没有的,很丝滑。
好了这里我们整个流程就走完了,当然Apple
官网并非写的这么简单。我写的比较简单一些,只是让大家看一下我的大概思想。有兴趣的同学可以试着去研究一下,还有很多优化的点需要优化。
事实上整个实现过程并不是很难,但是咱们男人在妹纸面前说的大话不能食言呐!
立马给妹纸看一下(心想:“小样,迷不死你!” 这该死的魅力。。。。狗头)
。
6. 结尾
其实有在想,为什么不能给这些图片合并成一个视频,或者使用一张巨大的精灵图
,只请求一次图片?然后在让滚动和播放进度进行同步,不知道对于流畅度和文件大小不知道这样做是好一些还是差一些。当然自己水平有限,Apple
的开发人员也应该有他们的想法考虑,获取使用图片才是最好的选择。(哈哈哈,自己的胡思乱想!)