纯js实现Apple官网类似的滚动切换效果
效果描述
最近接了一个小特效!实现的效果类似苹果官网的一个跟随滚动切换页面一块区域的效果!下面演示的是小米机器人的一个效果!哈哈哈!Apple 官网的我没找到了!不过效果演示哪里使用的是苹果的资源
效果描述:当滚动到某一个位置的时候,继续滚动,滚动条虽然在动,但是页面内容只是当前容器内的动画变换,并且可以根据滚轮速度,上下,来控制播放的速度和前后!
具体可以到这个网站去体验!https://www.mi.com/cyberone?spmref=mihomepage.mtl_62f4e94fe03dd80001487322.1
实现大致思路
滚动条滚动但是内容固定的思路
首先如果想让滚动条在滚动的时候,滚动到我们的切换展示部分,为了描述我们称之为(SVS)ScrollViewShow
(通过滚动展示内容的组件)。
滚动到 SVS
的时候,此时继续滚动的话,滚动应该是控制 SVS
内部内容的切换,内容没有切换展示完成的时候,SVS
是持续固定在页面,不会随着滚动变换位置的
当内容展示完成,继续滚动,此时 SVS
就被滚动走了,继续展示页面的其他内容!
如果此时再往上滚! 滚入到 SVS
则是反向播放 SVS
内容!
我们首先想到了fixed
和sticky
! 但是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 元素
当触发了基于
top
、right
、bottom
和left
的值进行偏移。偏移值不会影响任何其他元素的位置。
看看下面的一个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;
}
首先我们发现,box在之前滚动的时候不会脱离正常的文档流,当滚动到指定的 top:30px
的时候,它就被粘在了那个位置!
这就是 sticky
的效果!它在指定的 top
、right
、bottom
和 left
会进行偏移!我们就是用这个来当作我们 SVS
组件的固定方案!
设计 SVS
HTML 框架
和上面的例子一样,我们需要一个外部足够高的容器 container,并且是在我们 document 标准文档流的目的有2个
- 有足够的区域可以滚动
- 把上下内容撑开!屏幕这一块单纯展示我们的
SVS
组件
容器内部我们使用一个 canvas
用来绘制,这个canvas
使用 sticky
top
根据自己的实际项目来定,比如上面展示的官网,本身有个固定的 header
!比如高度为 70px
那么这个top
就要设置为 70px
不能影响了 header
的固定效果了!
这里我们还原一下 放一个 height
70 的 header
固定在上面
SVS
页面框架
那么我们 SVS
的大体框架就这样了,当然如果你想加入文字展示 可以直接自定义标签!和 canvas
使用 绝对定位去处理就好了!
当我们初始化我们的canvas 高度肯定要占满视口的,宽度视情况而定! 为了更好的可用性,可以设置为 父级盒子的宽度!
这里我们就占满整个屏幕吧!
此时的页面效果!
计算帧动画和滚动距离的关系
如何知道滚动到某个位置的时候应该渲染哪一帧呢?
我们处理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))
根据上面的分析,得出开始的结论:
在持续滚动的过程中! container 距离 header的这个
container.getBoundingClientRect().top-70
距离这个值会越来越小,到0代表和header底部重合表示此时 container盒子在视口中进行滚动
container.getBoundingClientRect().top-70
变为负数,继续变小!(绝对值越来越大!)【注意70是当前页面header的高度,因为这个情况下的header是固定在页面的!】
那么我们分析一下这个滚动什么时候结束呢! 也就是 container
滚动到其底部能出现在页面的时候!
(记住:canvas 因为sticky,且与视口等宽高的原因,一直占满了整个界面!其实container本身还是在滚动的)我们可以给container里面加点其他内容,然后canvas设置一个透明度来验证这一点!
下一步,我们就是要去计算一下,实际滚动了多少的距离!才让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))
那么我们**实际有效切换滚动距离 **effectiveSwitchingHeight
,就是container的高度减去canvas的实际高度!
const effectiveSwitchingHeight = 5000 - cvs.height;
然后根据具体的偏移距离比上这个 effectiveSwitchingHeight
就可以求出滚动的百分比!然后就可以确定当前是渲染的哪一帧!
if (start) {
console.log(-containerScrollLengthFromHeader / effectiveSwitchingHeight); // 注意 containerScrollLengthFromHeader 的值为负值!
}
切换实现
了解了上面的原理后,其实剩下的工作就非常简单了!
首先介绍几个要用的东西
- https://thinkpixellab.com/pxloader/ 用于 HTML5 应用程序的 JavaScript 预加载器 预先加载我们全部的帧动画图片!
这里我们使用的素材地址都是 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) // 传入绘制函数,取对应帧数绘制
}
这样就完成了!
效果演示
完整代码
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))
评论区