文章目录
- 如何固定动画区域
- 创建项目
- MarsCode 设置样式
- MarsCode 优点1
- MarsCode 缺点
- MarsCode 优点2
- js实现动画
- 实现获取动画曲线的函数
- 为什么实现这个函数?
- 根据当前滚动位置,计算每一个元素不同的数值
- 更新 dom 的 style
- 更新 animationMap
- getDomAnimation
@豆包MarsCode
要写出钉钉官网动画,首先第一步就是分析钉钉官网动画是怎么实现的!
滑动滚轮发现到了动画这里,界面是不会继续滚动的,而是等待动画执行完成后,才继续滚动!我们要实现的第一点就是:如何固定动画区域!只有搞定这个问题,下一个问题才是如何实现动画!
如何固定动画区域
这是一个解析图:灰色部分是网页内容比较长,蓝色部分是动画的执行区域,相对来说比较高(不然滚动条一滚动就被带走了),但是只有这两个是不行的,还需要红色这个粘性定位的内容,等内容滚动到红色区域时,会一直固定,等父元素(蓝色部分)滚动到最上面的时候,底部会将红色部分一起带走(保证了红色区域不管怎么滚动,有一段时间一直在可视区域内)!
不懂粘性定位可以异步:css Position(定位) [第八天]
创建项目
现在我们一步一步来,配合我们的 MarsCode 完成代码 !首先在vscode里准备好我们的前端三件套文件!
其中:body 是灰色部分,playground 是蓝色部分,红色部分是 animation_container,这里只是模仿钉钉这个动画,所以里面用的是小方格 list-item!
MarsCode 设置样式
使用 MarsCode 设置样式!
MarsCode 优点1
简单注释,MarsCode 生成的代码还是挺好用的
MarsCode 缺点
但是这种有多个需求的,MarsCode 就不能特别好的提示了
MarsCode 优点2
可以通过尝试换书写注释来生成正确的代码,但是比较难试!
通过菜鸟不断的尝试,MarsCode 居然真的自己写出来了:
效果如下:
然后我们自己优化一下:
/* 通配符取消margin、padding */
* {margin: 0;padding: 0;
}
body {height: 4000px;
}
/* 设置header占满屏幕,背景白色,文字黑色100px */
.header {height: 100vh;width: 100%;background-color: #fff;color: #000;font-size: 100px;text-align: center;line-height: 100vh;
}.playground {height: 2000px;width: 100%;background-color: #000;
}
/* 设置animation_container为粘性定位,并占满屏幕 */
.animation_container {position: sticky;top: 0;height: 100vh;width: 100%;
}/* 设置list在animation_container的中间,list-item排列成2行7列,均匀分布 */
.list {position: absolute;top: 50%;left: 50%;transform: translate(-50%, -50%);display: grid;grid-template-columns: repeat(7, 1fr);grid-template-rows: repeat(2, 1fr);grid-gap: 50px;
}/* 请设置list-item的宽高为100px,高宽比为1:1 */
.list-item {width: 100px;height: 100px;aspect-ratio: 1/1;border-radius: 5%;
}/* list-item有14个,请均匀分布在list中,且2行7列,颜色各不相同 */
.list-item:nth-child(1) {background-color: #f00;
}
.list-item:nth-child(2) {background-color: #0f0;
}
.list-item:nth-child(3) {background-color: #00f;
}
.list-item:nth-child(4) {background-color: #ff0;
}
.list-item:nth-child(5) {background-color: #0ff;
}
.list-item:nth-child(6) {background-color: #f0f;
}
.list-item:nth-child(7) {background-color: #fff;
}
.list-item:nth-child(8) {background-color: #f00;
}
.list-item:nth-child(9) {background-color: #0f0;
}
.list-item:nth-child(10) {background-color: #00f;
}
.list-item:nth-child(11) {background-color: #ff0;
}
.list-item:nth-child(12) {background-color: #0ff;
}
.list-item:nth-child(13) {background-color: #f0f;
}
.list-item:nth-child(14) {background-color: #fff;
}
结果
到此元素结构和样式搞定了!
js实现动画
现在到了实现动画的阶段了,首先要知道css动画是什么?css就是数值随时间的变化,但是这里没有时间,细心的同学就会发现虽然和时间无关,但是和滚动条相关了,所以要抽象成这个图
值随滚动条变化的图,而不是时间了!
实现获取动画曲线的函数
现在就是要写一个动画曲线函数,给我一个x,算出一个y,为了知道曲线函数需要传入scrollStart、scrollEnd、valueStart、valueEnd,然后剩下的就交给 MarsCode了!
写函数,然后输入几个参数后,MarsCode 自己就帮助我们生成好了!
自己想要完善一下(写的时候,MarsCode 也可以直接帮你,实在是牛)
function createAnimation(scrollStart, scrollEnd, valueStart, valueEnd) {return function (x) {if (x < scrollStart) return valueStart;if (x > scrollEnd) return valueEnd;return (valueStart +((valueEnd - valueStart) * (x - scrollStart)) / (scrollEnd - scrollStart));};
}
为什么实现这个函数?
就是用于备用,直接传值进函数,获得一个根据滚动条和属性直接的关系函数(MarsCode也直接帮你完成,但是暂时不需要)
这里解释一下
opacity 这个函数就是:滚动条从0 到 100,那么透明度就从 0 到 1的函数曲线!
当然这里的滚动值不是很对,这个就是后面我们按需求修改!
根据当前滚动位置,计算每一个元素不同的数值
既然是动态变化的值,那肯定是这样的数据结构
所以要建立映射关系,将对象给每一个 dom
// 为每一个元素映射一个动画
const animationMap = new Map();
const items = document.querySelectorAll(".list-item");
const playground = document.querySelector(".playground");
const list = document.querySelector(".list");
然后就是两件事:
- 更新 animationMap
- 更新 dom 的 style
所以写出这两个函数
// 更新动画隐射
function updateAnimationMap() {}// 将map结构用于元素
function updateStyle() {}
更新 dom 的 style
其中比较好写的就是 更新 dom 的 style
思路:获取整个界面的滚动距离,循环 Map 去获取上面的对象,再循环对象获取值并调用函数!
有了思路,MarsCode 会直接帮你写好(写两个for,自己就出来了)
// 将map结构用于元素
function updateStyle() {const scrollY = window.scrollY;for (const [element, animation] of animationMap) {for (const key in animation) {// 自己添加的注释// 调用每个dom对应的动画函数// 动画函数的参数是当前的scrollYelement.style.setProperty(key, animation[key](scrollY));}}
}
写完之后就调用,MarsCode 也会帮你完成
更新 animationMap
思路:循环dom节点,并设置Map
写一个for,MarsCode 直接就帮你写好了
只是这里没这么简单,设置的是那个数据结构的对象,所以我们只保留设置部分!
function getDomAnimation(dom) {}// 更新动画隐射
function updateAnimationMap() {for (const item of items) {animationMap.set(item, getDomAnimation(dom));}
}
getDomAnimation
返回的是一个对象
return {opacity,transform,
};
这里的 opacity,transform 又可以通过 createAnimation 来获取!然后发现 scrollStart,scrollEnd 所有的dom是一样的,所以直接当参数传入!
function getDomAnimation(scrollStart, scrollEnd, dom) {const opacity = createAnimation(scrollStart, scrollEnd, 0, 1);const transform = createAnimation(scrollStart, scrollEnd, 0, 100);return {opacity,transform,};
}// 更新动画隐射
function updateAnimationMap() {for (const item of items) {animationMap.set(item, getDomAnimation(scrollStart, scrollEnd, item));}
}
确定好什么时候滚动、以及什么时候滚动结束!
// 更新动画隐射
function updateAnimationMap() {const playgroundRect = playground.getBoundingClientRect();const scrollY = window.scrollY;const scrollStart = playgroundRect.top + scrollY;const scrollEnd = playgroundRect.bottom + scrollY - window.innerHeight;for (const item of items) {animationMap.set(item, getDomAnimation(scrollStart, scrollEnd, item));}
}
这个时候透明度的变化已经完成!
剩下的就是transform的一些属性,也可以按照 opacity 一样,不同的是这个 transformx\transformy ,需要计算
function getDomAnimation(scrollStart, scrollEnd, dom) {const opacity = createAnimation(scrollStart, scrollEnd, 0, 1);const scale = createAnimation(scrollStart, scrollEnd, 0.5, 1);const Xtransform = createAnimation(scrollStart, scrollEnd, ?, 0);const Ytransform = createAnimation(scrollStart, scrollEnd, ?, 0);const transform = (x) => {return `scale(${scale(x)}) translate(${Xtransform(x)}px, ${Ytransform(x)}px)`;};return {opacity,transform,};
}
都是从中间出来的,所以
function getDomAnimation(scrollStart, scrollEnd, dom) {const opacity = createAnimation(scrollStart, scrollEnd, 0, 1);const scale = createAnimation(scrollStart, scrollEnd, 0.5, 1);const { clientWidth, clientHeight, offsetTop, offsetLeft } = dom;console.log(clientWidth, clientHeight, offsetTop, offsetLeft);const listRect = list.getBoundingClientRect();const Xtransform = createAnimation(scrollStart,scrollEnd,listRect.width / 2 - clientWidth / 2 - offsetLeft,0);const Ytransform = createAnimation(scrollStart,scrollEnd,listRect.height / 2 - clientHeight / 2 - offsetTop,0);const transform = (x) => {return `translate(${Xtransform(x)}px, ${Ytransform(x)}px) scale(${scale(x)}) `;};return {opacity,transform,};
}
至此,效果基本实现,再就是动画需要延时,通过data-设置delay来实现
<div data-delay="0" class="list-item"></div>
<div data-delay="1" class="list-item"></div>
<div data-delay="2" class="list-item"></div>
<div data-delay="3" class="list-item"></div>
<div data-delay="2" class="list-item"></div>
<div data-delay="1" class="list-item"></div>
<div data-delay="0" class="list-item"></div>
<div data-delay="0" class="list-item"></div>
<div data-delay="1" class="list-item"></div>
<div data-delay="2" class="list-item"></div>
<div data-delay="3" class="list-item"></div>
<div data-delay="2" class="list-item"></div>
<div data-delay="1" class="list-item"></div>
<div data-delay="0" class="list-item"></div>
function getDomAnimation(scrollStart, scrollEnd, dom) {// 注意这个不要超过滚动距离,不然就会导致超过的动画瞬间完成scrollStart += dom.dataset.delay * 200;const opacity = createAnimation(scrollStart, scrollEnd, 0, 1);const scale = createAnimation(scrollStart, scrollEnd, 0.5, 1);const { clientWidth, clientHeight, offsetTop, offsetLeft } = dom;const listRect = list.getBoundingClientRect();const Xtransform = createAnimation(scrollStart,scrollEnd,listRect.width / 2 - clientWidth / 2 - offsetLeft,0);const Ytransform = createAnimation(scrollStart,scrollEnd,listRect.height / 2 - clientHeight / 2 - offsetTop,0);const transform = (x) => {return `translate(${Xtransform(x)}px, ${Ytransform(x)}px) scale(${scale(x)}) `;};return {opacity,transform,};
}
到此,钉钉官网动画基本上主要内容完成了,剩下的都是些图标什么的,还是比较简单的!
体验完 MarsCode ,感觉智能提示等都很不错,但是也有不足的地方,就是有些没有确定思路的地方,如果你不会,那么提示的大概率也是错误的!所以 AI 只是辅助,重要的还是自己的能力过关!