AirPods Pro都买了吧,但是Apple官网的动效你会做吗?

5,777 阅读4分钟

Snipaste_2022-09-16_15-01-35.png “我正在参加「码上掘金挑战赛」详情请看:码上掘金挑战赛来了!

苹果每年一度的发布会,前不久刚刚结束。毫无疑问今年的爱疯,依旧没啥创新没有新意。但是灵动岛设计,可谓是受到了各大媒体的追捧。部分人更是喊着 支持华为,我买苹果老段子了。

发布会的第二天,就看到部门的妹纸在看新出的AirPods Pro,还说道:
“这个官网做的真好看,很有科技感!苹果的程序员就是厉害!”
??? 我一脸不服气说道:
“这就厉害了? 让我看看,看我不实现给你看。”
然后自己仔细看了下,确实很炫酷很有冲击力,大家可以去看一下 AirPods Pro。看起来挺有难度的,不过我可是搞前端的,男人不能在女人面前说不行啊。(一生好强的男人。。。)
走着, 不能在妹妹面前丢人。。。

1. 苹果官网

首先,我们先来看一下,苹果实现的效果。

SDGIF_Rusult_1.gif

2. 思考

看到这里,我脑子里已经在思考这是怎么实现的了。首先我想到的是。。。 相信大家都很聪明,和我有这差不多的童年,这都是我们小时候玩过的。废话不多说上图。

20220916-152623.gif 看到了吧,火柴人 都玩过吧。我的想法就是创建一个动画,快速连续切换一系列图像。就像翻书一样,让我们的火柴人可以动起来。
这时,我们就可以根据用户滚动的位置同步图片的每一帧,向上或者向下时,视觉上看起来像是一整套无缝的动画。

3. 分析

然后我们打开官网F12 审查元素,找到这一元素。我们可以看到Apple用的Canvas

Snipaste_2022-09-16_14-42-16.png 这时,我们就可以初步判断了,我们的想法大概是一致的。然后我就切换到了Network 选择img,哈哈哈! 看到这里时 我就大概已经知道我该怎么做了。

Snipaste_2022-09-16_15-38-05.png 看一下我图片标的红框位置,这里AppleUI切好了一整套流程的每一帧。并且我们可以轻松通过末尾的路径来精准渲染每帧的图像。拿到了现成的图片路径,我们废话不多说直接开始码。

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 了,下面我们将根据用户滚动控制图片的每一帧渲染。

这里我们需要知道:

  1. 滚动开始结束的地方
  2. 滚动的多少
  3. 滚动进度对应的图片

我们用 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的开发人员也应该有他们的想法考虑,获取使用图片才是最好的选择。(哈哈哈,自己的胡思乱想!)