目 录CONTENT

文章目录

纯js实现Apple官网类似的滚动切换效果

Hello!你好!我是村望~!
2023-04-17 / 3 评论 / 5 点赞 / 354 阅读 / 6,336 字
温馨提示:
我不想探寻任何东西的意义,我只享受当下思考的快乐~

纯js实现Apple官网类似的滚动切换效果

效果描述

最近接了一个小特效!实现的效果类似苹果官网的一个跟随滚动切换页面一块区域的效果!下面演示的是小米机器人的一个效果!哈哈哈!Apple 官网的我没找到了!不过效果演示哪里使用的是苹果的资源

1

效果描述:当滚动到某一个位置的时候,继续滚动,滚动条虽然在动,但是页面内容只是当前容器内的动画变换,并且可以根据滚轮速度,上下,来控制播放的速度和前后!

具体可以到这个网站去体验!https://www.mi.com/cyberone?spmref=mihomepage.mtl_62f4e94fe03dd80001487322.1

实现大致思路

滚动条滚动但是内容固定的思路

首先如果想让滚动条在滚动的时候,滚动到我们的切换展示部分,为了描述我们称之为(SVS)ScrollViewShow(通过滚动展示内容的组件)。

滚动到 SVS的时候,此时继续滚动的话,滚动应该是控制 SVS内部内容的切换,内容没有切换展示完成的时候,SVS是持续固定在页面,不会随着滚动变换位置的

当内容展示完成,继续滚动,此时 SVS 就被滚动走了,继续展示页面的其他内容!

如果此时再往上滚! 滚入到 SVS 则是反向播放 SVS 内容!

我们首先想到了fixedsticky! 但是fixed的效果是持续固定在某个位置的!从页面的效果来看!当上方预先有一些组件的情况,是不太适合的。使用 fixed 的话,SVS 会持续固定再那个位置!就算手动去切换 postion 后续的位置计算也是比较繁琐

那么这种效果用sticky 是最适合的!

内部切换的效果思路

其实打开控制台来看!其实切换的都是每一张帧动画图片!

然后我们根据滚动计算,去切换 canvas 绘制图片就好了!这个计算具体后面去分析!

这里提供了一个 node 脚本爬取图片,里面有大疆 apple 官网的各种图片 可以对比着官网图片地址改一下!

const https = require('https');
const fs = require('fs');

for (let i = 0; i < 100; i++) {
    let index = ""
    // if (i < 10) {
    //     index = `000${i}`
    // } else {
    //     index = `00${i}`
    // }
    index = i
    console.log(index)
    const downloadPath = `./downloads/${index}.jpg`;
    // const imageUrl = `https://cdn.cnbj1.fds.api.mi-img.com/product-images/cyberone/sec3/sec3-${index}.jpg`;
    // const imageUrl = `https://www.apple.com.cn/105/media/us/airpods-pro/2022/d2deeb8e-83eb-48ea-9721-f567cf0fffa8/anim/hero/small/${index}.jpg`;
    const imageUrl = `https://dji-official-fe.djicdn.com/assets/uploads/p/1e60084e-98af-4b1b-a980-75132167f977/obstacle-pc/${index}.jpg`;
    // const imageUrl = `https://dji-official-fe.djicdn.com/assets/uploads/p/5086ad85-ec52-4384-a5a9-8268b9ab5ab9/${index}.webp`;
    https.get(imageUrl, (response) => {
        response.pipe(fs.createWriteStream(downloadPath));
    });

}

sticky 复习

元素根据正常文档流进行定位,然后相对它的最近滚动祖先(nearest scrolling ancestor)和 containing block最近块级祖先 nearest block-level ancestor),包括 table-related 元素

当触发了基于 toprightbottomleft 的值进行偏移。偏移值不会影响任何其他元素的位置。

看看下面的一个demo,首先

首先定义HTML 结构

<p>1111</p>
<p>1111</p>
<p>1111</p>
<p>1111</p>
<p>1111</p>
<p>1111</p>
<p>1111</p>
<p>1111</p>
<div class="box">

</div>

然后写一点css,为了让body滚动起来,给他 5000的高度!

body {
  height: 5000px;
}
.box {
  width: 50px;
  height: 50px;
  background: #f00;
  position: sticky;
  top: 30px;
}

动画 (21)

首先我们发现,box在之前滚动的时候不会脱离正常的文档流,当滚动到指定的 top:30px 的时候,它就被粘在了那个位置!

这就是 sticky 的效果!它在指定的 toprightbottomleft 会进行偏移!我们就是用这个来当作我们 SVS 组件的固定方案!

设计 SVS HTML 框架

和上面的例子一样,我们需要一个外部足够高的容器 container,并且是在我们 document 标准文档流的目的有2个

  • 有足够的区域可以滚动
  • 把上下内容撑开!屏幕这一块单纯展示我们的 SVS 组件

容器内部我们使用一个 canvas 用来绘制,这个canvas使用 sticky top根据自己的实际项目来定,比如上面展示的官网,本身有个固定的 header !比如高度为 70px

那么这个top就要设置为 70px 不能影响了 header的固定效果了!

这里我们还原一下 放一个 height 70 的 header 固定在上面

image-20230417103112229

SVS 页面框架

image-20230417103330119

那么我们 SVS 的大体框架就这样了,当然如果你想加入文字展示 可以直接自定义标签!和 canvas 使用 绝对定位去处理就好了!

当我们初始化我们的canvas 高度肯定要占满视口的,宽度视情况而定! 为了更好的可用性,可以设置为 父级盒子的宽度!

这里我们就占满整个屏幕吧!

image-20230417103516529

image-20230416222607995

此时的页面效果!

Apr-17-2023 10-45-43

计算帧动画和滚动距离的关系

如何知道滚动到某个位置的时候应该渲染哪一帧呢?

我们处理canvas内部切换到逻辑,就是从进入到container滚动,到container滚动到最底部出现的这段过程!

根据滚动container滚动的实际距离和要变换的帧数,来决定任意滚动距离要渲染的帧数!

比如帧动画有100帧,整个container滚动到底部需要滚动到距离有3000px,那就是 3000/100 = 30px.那么就是每次滚动 30px 的时候就切换一帧

向下滚动就切换下一帧,向上滚动就切换上一帧!

如果当前的滚动距离为 300 代表已经滚动了整个滚动距离的 十分之一,对于 100 帧动画,此时应该渲染第10帧的动画!

公式就是 当前滚动的百分比*总帧数 = 当前应该渲染的帧数!

找到开始切换那一刻,和结束切换那一刻!

开始切换到时机,就是container的top值距离视口顶部的距离 等于 0 的时候开始的!

container.getBoundingClientRect().top // 判断container距离顶部的距离 <= 0 
function scrollHandler(e) {
    // 70 对应header的高度 
    console.log("距离header", container.getBoundingClientRect().top - 70)
    const start = container.getBoundingClientRect().top - 70 <= 0
    if (start) {
        console.log("开始切换了")
    }
}
document.addEventListener("scroll", _.throttle(scrollHandler, 1000))

Apr-17-2023 11-50-55

根据上面的分析,得出开始的结论:

在持续滚动的过程中! container 距离 header的这个 container.getBoundingClientRect().top-70 距离这个值会越来越小,到0代表和header底部重合

表示此时 container盒子在视口中进行滚动 container.getBoundingClientRect().top-70 变为负数,继续变小!(绝对值越来越大!)

【注意70是当前页面header的高度,因为这个情况下的header是固定在页面的!】

那么我们分析一下这个滚动什么时候结束呢! 也就是 container 滚动到其底部能出现在页面的时候!

(记住:canvas 因为sticky,且与视口等宽高的原因,一直占满了整个界面!其实container本身还是在滚动的)我们可以给container里面加点其他内容,然后canvas设置一个透明度来验证这一点!

Apr-17-2023 12-02-13

下一步,我们就是要去计算一下,实际滚动了多少的距离!才让container整个滚动结束,然后我们根据这段距离套用之前分析的公式就可以合理的算出 平均滚动距离应该渲染的帧数了!

其实很简单!

  • 首先我们 container 高度是固定的 5000

  • container 的底部开始出现在视口中,代表了滚动结束!

  • 【注意,sticky 是相对它的最近滚动祖先的当container滚动走了sticky元素就不会继续sticky了! 】

5000 为 container 高度!当某个时候 container 在视口中的高度不足以完全展示 canvas!此时继续滚动 canvas 就会跟随者滚走了,代表切换结束!
足够理想的情况下 这个时候 container相对于 header底部的滚动偏移量 + canvas高度 就是container的高度了

function scrollHandler(e) {
    // container滚动的时候 相对于header偏移距离 一开始container相对hea
    const containerScrollLengthFromHeader = container.getBoundingClien
    const start = containerScrollLengthFromHeader <= 0 // 当为0的时候

    console.log(-containerScrollLengthFromHeader + cvs.height) 
    
    // 5000 为 container 高度!当某个时候container在视口中的高度不足以
    // 足够理想的情况下 这个时候 container相对于 header底部的滚动偏移
    const end = -containerScrollLengthFromHeader + cvs.height >= 5000;
    if (start) {
        console.log("开始切换了")
    }
    if (end) {
        alert("切换结束了")
    }
}
document.addEventListener("scroll", _.throttle(scrollHandler, 1000))

Apr-17-2023 12-13-13

那么我们**实际有效切换滚动距离 **effectiveSwitchingHeight,就是container的高度减去canvas的实际高度!

const effectiveSwitchingHeight = 5000 - cvs.height;

然后根据具体的偏移距离比上这个 effectiveSwitchingHeight 就可以求出滚动的百分比!然后就可以确定当前是渲染的哪一帧!

if (start) {
    console.log(-containerScrollLengthFromHeader / effectiveSwitchingHeight); // 注意 containerScrollLengthFromHeader 的值为负值!
}

Apr-17-2023 12-26-32

切换实现

了解了上面的原理后,其实剩下的工作就非常简单了!

首先介绍几个要用的东西

这里我们使用的素材地址都是 https://s3-us-west-2.amazonaws.com/s.cdpn.io/2002878/iphone-se.01.png 类似这样的 下一帧的动画图片名称就是 02.png 以此类推

所以我们这里先使用 预加载 加载全部的图片

// 预加载
const loader = new PxLoader()
const images = []
for (let i = 0; i < 85; i++) {
    images[i] = loader.addImage(`https://s3-us-west-2.amazonaws.com/s.cdpn.io/2002878/iphone-se.${('0' + (i + 1)).slice(-2)}.png`)
}

加载的图片资源,放在images数组中!

当加载完成后,我们就去绘制第一张!

// 加载完绘制第一张!
loader.addCompletionListener(() => DrawFrame())

function DrawFrame(current = 0) {
    context.drawImage(images[current], 0, 0, cvs.width, cvs.height)
}

loader.start() // 开始执行预加载

DrawFrame 传入当前的绘制的帧数默认就是0,具体绘制的比例可以根据素材具体情况调整,这里就不做处理了!

然后回到滚动到函数,根据百分比计算帧数!

if (start && !end) {
    // 开始切换,并且尚未结束的时候!
    const percent = -containerScrollLengthFromHeader / effectiveSwitchingHeight; // 算出滚动的百分比
    const currentFrameIdx = Math.floor(images.length * percent) // 根据百分比算出帧数 取整
    DrawFrame(currentFrameIdx) // 传入绘制函数,取对应帧数绘制
}

这样就完成了!

效果演示

Apr-17-2023 12-51-24

完整代码

HTML

<!DOCTYPE html>
<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>
    <style>
      * {
        padding: 0;
        margin: 0;
      }
      html,
      body {
        width: 100%;
        text-align: center;
        box-sizing: border-box;
      }
      header {
        width: 100%;
        height: 70px;
        line-height: 70px;
        background: #000;
        text-align: center;
        color: #fff;
        /* 头部固定在顶部 且不影响其他元素在文档里的位置! */
        position: sticky;
        top: 0;
        z-index: 1;
      }
      .container {
        /* 有足够多的空间滚动! */
        height: 5000px;
        display: flex;
        flex-direction: column;
        justify-content: space-around;
      }
      .stickyCanvas {
        /* top 70和header的高度对应! */
        top: 70px;
        position: sticky;
        background-color: rgb(0, 0, 0);
      }
    </style>
  </head>
  <body>
    <header>
      <h1>Header</h1>
    </header>
    <!-- 一些其他内容 -->
    <div>
      <h1>清单</h1>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
    </div>
    <ul>
      <li data-pid="f_Pj1Lzs">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.joshwcomeau.com/blog/the-end-of-frontend-development/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >前端开发走向终结</a
        >
        - joshwcomeau.com
      </li>
      <li data-pid="52diSTbu">
        <a
          href="https://link.zhihu.com/?target=https%3A//web.dev/import-maps-in-all-modern-browsers/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >JavaScript import maps 特性如今已跨浏览器支持</a
        >
        - web.dev
      </li>
      <li data-pid="ukW3pPlr">
        <a
          href="https://link.zhihu.com/?target=https%3A//dev.to/hemanth/updates-from-the-95th-tc39-meeting-ne5"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >来自第95次 TC39 会议的信息更新</a
        >
        - dev.to
      </li>
      <li data-pid="SFGZY3V3">
        <a
          href="https://link.zhihu.com/?target=https%3A//code.visualstudio.com/updates/v1_77"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >Visual Studio Code 2023年3月更新进展</a
        >
        - visualstudio.com
      </li>
      <li data-pid="3CC_cGfA">
        <a
          href="https://link.zhihu.com/?target=https%3A//react.dev/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >React Lab:我们正在进行的工作- 2023年3月</a
        >
        -
      </li>
      <li data-pid="e4lQHR-V">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.amitmerchant.com/create-and-download-text-files-using-javascript/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >用 JavaScript 生成与下载文本文件</a
        >
        - amitmerchant.com
      </li>
      <li data-pid="39IlPcNo">
        <a
          href="https://link.zhihu.com/?target=https%3A//betterprogramming.pub/all-javascript-and-typescript-features-of-the-last-3-years-629c57e73e42"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >过去三年 JavaScript 与 TypeScript 语言新特性汇总</a
        >
        - betterprogramming.pub
      </li>
      <li data-pid="HoBbSmbZ">
        <a
          href="https://link.zhihu.com/?target=https%3A//devblogs.microsoft.com/typescript/typescripts-migration-to-modules/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >使用 ECMAScript Modules 迁移 TypeScript 代码仓库</a
        >
        - microsoft
      </li>
      <li data-pid="iJdvnCcR">
        <a
          href="https://link.zhihu.com/?target=https%3A//marvinh.dev/blog/speeding-up-javascript-ecosystem-part-4/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >JavaScript 生态运行加速——npm scripts</a
        >
        - marvinh.dev
      </li>
      <li data-pid="rEgIw4X2">
        <a
          href="https://link.zhihu.com/?target=https%3A//deno.com/blog/package-json-support"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >为什么我们在 Deno 中增加对 package.json 的支持</a
        >
        - deno.com
      </li>
      <li data-pid="ve5qDmE6">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.robinwieruch.de/react-starter/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >在2023年要如何新建一个 React 项目</a
        >
        - robinwieruch.de
      </li>
      <li data-pid="25PUGDKS">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.joshwcomeau.com/react/common-beginner-mistakes/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >React 开发人员常见的新手错误</a
        >
        - joshwcomeau.com
      </li>
      <li data-pid="tsQG9nb9">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.zachleat.com/web/site-generator-review/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >JavaScript 站点生成工具年度回顾(2023版)</a
        >
        - zachleat.com
      </li>
      <li data-pid="Iut0RZzp">
        <a
          href="https://link.zhihu.com/?target=https%3A//dev.to/codux/experiments-with-the-javascript-garbage-collector-2ae3"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >用 JavaScript 垃圾回收器做的一些实验</a
        >
        - dev.to
      </li>
      <li data-pid="MLBE4jTU">
        <a
          href="https://link.zhihu.com/?target=https%3A//bejamas.io/blog/practical-guide-to-astro-js-framework/"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >Astro JS 框架:一个构建运行更优网站的实践指南</a
        >
        - bejamas.io
      </li>
      <li data-pid="5w7tUshf">
        <a
          href="https://link.zhihu.com/?target=https%3A//www.electronjs.org/blog/10-years-of-electron"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >欢庆 Electron 开源十周年</a
        >
        - electronjs.org
      </li>
      <li data-pid="qj7MuPlk">
        <a
          href="https://link.zhihu.com/?target=https%3A//changelog.com/jsparty/267"
          class="wrap external"
          target="_blank"
          rel="nofollow noreferrer"
          data-za-detail-view-id="1043"
          >React 未来展望</a
        >
        - changelog.com
      </li>
    </ul>

    <div class="container">
      <!-- 这里你可以放一些其他元素,比如文字丰富切换到效果 -->
      <canvas class="stickyCanvas"></canvas>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
      <p>
        清单 本期话题包含前端开发未来展望、React 框架未来展望、Web API、TC39
        会议纪要、Visual Studio Code、React 开源进展、JavaScript
        开发实践、语言特性介绍、TypeScript 仓库变动进展、npm
        scripts、deno、React 实践、JavaScript 站点生成工具回顾、JavaScript
        垃圾回收器、astro.js、electron 等。
      </p>
    </div>

    <!-- 一些其他内容 -->
    <footer>
      <ul>
        <li data-pid="1N8bQQ36">
          <a
            href="https://link.zhihu.com/?target=https%3A//github.com/pnpm/pnpm/releases/tag/v8.1.0"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >pnpm v8.1.0</a
          >
        </li>
        <li data-pid="M0nuOwbG">
          <a
            href="https://link.zhihu.com/?target=https%3A//nodejs.org/en/blog/release/v16.20.0"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >Node.js v16.20.0</a
          >
        </li>
        <li data-pid="fL9afy0y">
          <a
            href="https://link.zhihu.com/?target=https%3A//thecodebarbarian.com/introducing-mongoose-7.html"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >Mongoose 7.0</a
          >
        </li>
        <li data-pid="A--d2Cnc">
          <a
            href="https://link.zhihu.com/?target=https%3A//github.com/eslint/eslint/releases/tag/v8.36.0"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >eslint v8.36.0</a
          >
        </li>
        <li data-pid="usAt7oAr">
          <a
            href="https://link.zhihu.com/?target=https%3A//blog.emberjs.com/ember-released-4-11/"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >Ember 4.11</a
          >
        </li>
        <li data-pid="sGKza6lX">
          <a
            href="https://link.zhihu.com/?target=https%3A//github.com/puppeteer/puppeteer/releases/tag/puppeteer-core-v19.8.0"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >puppeteer-core: v19.8.0</a
          >
        </li>
        <li data-pid="zpL1K2w9">
          <a
            href="https://link.zhihu.com/?target=https%3A//deno.com/blog/v1.31"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >Deno 1.31</a
          >
        </li>
        <li data-pid="IEAoHFYl">
          <a
            href="https://link.zhihu.com/?target=https%3A//xenova.github.io/transformers.js/"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >开源库介绍 - Transformers.js</a
          >
        </li>
        <li data-pid="QDUyB-t1">
          <a
            href="https://link.zhihu.com/?target=https%3A//devblogs.microsoft.com/typescript/announcing-typescript-5-0/"
            class="wrap external"
            target="_blank"
            rel="nofollow noreferrer"
            data-za-detail-view-id="1043"
            >TypeScript 5.0</a
          >
        </li>
      </ul>
    </footer>
  </body>
  <script src="https://cdn.jsdelivr.net/npm/pxloader@1.1.2/PxLoader.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/pxloader@1.1.2/PxLoaderImage.js"></script>
  <script src="https://cdn.bootcdn.net/ajax/libs/lodash.js/4.17.21/lodash.js"></script>
  <script src="./index.js"></script>
</html>

Javascript

const container = document.querySelector(".container");
const cvs = document.querySelector(".stickyCanvas");
const context = cvs.getContext('2d')

// 预加载
const loader = new PxLoader()
const images = []
for (let i = 0; i < 85; i++) {
    images[i] = loader.addImage(`https://s3-us-west-2.amazonaws.com/s.cdpn.io/2002878/iphone-se.${('0' + (i + 1)).slice(-2)}.png`)
}

// 加载完绘制第一张!
loader.addCompletionListener(() => DrawFrame())

function DrawFrame(current = 0) {
    context.drawImage(images[current], 0, 0, cvs.width, cvs.height)
}

loader.start()
function InitCanavs() {
    cvs.width = window.innerWidth
    cvs.height = window.innerHeight - 70 // 减去的70也是和header高度对应!
}

InitCanavs()
window.addEventListener("resize", InitCanavs)

function scrollHandler(e) {
    // container滚动的时候 相对于header偏移距离 一开始container相对header有一定距离,且在header下面为正值
    const containerScrollLengthFromHeader = container.getBoundingClientRect().top - 70; // 70 对应header的高度 
    const start = containerScrollLengthFromHeader <= 0 // 当为0的时候代表还是交汇

    // 5000 为 container 高度!当某个时候container在视口中的高度不足以完全展示canvas!此时继续滚动canvas就会跟随者滚走了,代表切换结束!
    // 足够理想的情况下 这个时候 container相对于 header底部的滚动偏移量 + canvas高度 就是container的高度了
    const end = -containerScrollLengthFromHeader + cvs.height >= 5000;
    const effectiveSwitchingHeight = 5000 - cvs.height;
    if (start && !end) {
        // 开始切换,并且尚未结束的时候!
        const percent = -containerScrollLengthFromHeader / effectiveSwitchingHeight; // 算出滚动到百分比
        const currentFrameIdx = Math.floor(images.length * percent) // 根据百分比算出帧数 取整
        DrawFrame(currentFrameIdx) // 传入绘制函数,取对应帧数绘制
    }
    if (end) {
        console.log("结束了")
    }
}
document.addEventListener("scroll", _.throttle(scrollHandler, 10))
5

评论区