在前端开发中,DOM 动画是指通过 JavaScript 操作 DOM 元素的样式或属性,实现页面元素的平移、旋转、缩放等动画效果。常用的 DOM 动画方式包括使用 CSS3 动画、CSS3 过渡、JavaScript 动画库等。通过使用 DOM 动画,可以为页面增加更加生动、有趣的交互体验,提升用户的满意度和使用体验。
一、基本动画
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box {
width: 100px;
height: 100px;
background: red;
position: absolute;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div class="box"></div>
</body>
<script>
const box = document.querySelector(".box");
// 用于存储计时器的标志
let t;
// 目标值
const target = 302;
// 步长
let speed = 5;
document.onclick = function () {
if (t) return;
t = setInterval(() => {
// 剩下的距离,不足一步
if (target - box.offsetLeft < speed) {
// 强行将元素设置到终点
box.style.left = target + "px";
// 停止计时器
clearInterval(t);
t = undefined;
} else {
// 正常改变元素位置(运动过程...)
box.style.left = box.offsetLeft + speed + "px";
}
}, 30);
}
// 1. 运动的元素:box
// 2. 步长:一步走多远:1px
// 3. 频率:每个单位时间是多少:30ms
// DOM动画,建议频率30ms左右
// 一秒能识别24张图片
// 1000/24 约等于 41
// 考虑DOM性能的情况下,取值为30ms
</script>
</html>
二、进度条
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 400px;height: 200px;border: solid 1px black;position: relative;}
.box p{margin: 0;position: absolute;height: 20px;background: blue;color: #fff;text-align: center;line-height: 20px;left:0;top:90px;}
</style>
</head>
<body>
<div class="box">
<p></p>
</div>
</body>
<script>
const p = document.querySelector(".box p")
const speed = 3;
const target = 400;
let t;
document.onclick = function(){
if(t) return;
t = setInterval(()=>{
if(target - p.offsetWidth < speed){
clearInterval(t);
p.style.width = target + "px";
p.innerText = "100%";
}else{
p.style.width = p.offsetWidth + speed + "px";
// 计算百分比
p.innerText = (p.offsetWidth / target * 100).toFixed(2) + "%";
}
}, 30);
}
</script>
</html>
三、重力小球
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 700px;height: 600px;border: solid 1px black;position: relative;margin: 0 auto;}
.xbox{width: 100px;height: 100px;background: red;position: absolute;left:0;top:0;border-radius: 50%;}
</style>
</head>
<body>
<div class="box">
<div class="xbox"></div>
</div>
</body>
<script>
const xbox = document.querySelector(".xbox");
let t;
const target = 500;
let speed = 5;
document.onclick = function(){
if(t) return;
t = setInterval(()=>{
// 重力加速:增加步长
speed++;
// 下方目标
if(target - xbox.offsetTop < speed ){
xbox.style.top = target + "px";
// 到终点后,将步长取反,实现反方向的运动
speed = -speed * 0.9;
// 到达底部后,如果步长的绝对值小于1表示弹出起来了,停止计时器
if(Math.abs(speed) < 1){
clearInterval(t);
}
}else{
xbox.style.top = xbox.offsetTop + speed + "px";
}
}, 30);
}
</script>
</html>
四、跳动小球
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 700px;height: 600px;border: solid 1px black;position: relative;margin: 0 auto;}
.xbox{width: 100px;height: 100px;background: red;position: absolute;left:0;top:0;border-radius: 50%;}
</style>
</head>
<body>
<div class="box">
<div class="xbox"></div>
</div>
</body>
<script>
const xbox = document.querySelector(".xbox");
let t;
const target = 500;
let speed = 5;
let speedX = 10;
const targetRight = 600;
document.onclick = function(){
if(t) return;
t = setInterval(()=>{
// 下方目标
if(target - xbox.offsetTop < speed ){
xbox.style.top = target + "px";
speed = -speed;
}else{
xbox.style.top = xbox.offsetTop + speed/2 + "px";
}
// 上方目标
if(xbox.offsetTop < 0 ){
xbox.style.top = 0 + "px";
speed = -speed;
}else{
xbox.style.top = xbox.offsetTop + speed/2 + "px";
}
// 右侧目标
if(targetRight - xbox.offsetLeft < speedX ){
xbox.style.left = targetRight + "px";
speedX = -speedX;
}else{
xbox.style.left = xbox.offsetLeft + speedX/2 + "px";
}
// 左侧目标
if(xbox.offsetLeft < 0 ){
xbox.style.left = 0 + "px";
speedX = -speedX;
}else{
xbox.style.left = xbox.offsetLeft + speedX/2 + "px";
}
}, 30);
}
</script>
</html>
五、圆周运动
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 20px;height:20px;background: red;position: absolute;left:0;top:0;border-radius: 50%;}
</style>
</head>
<body>
</body>
<script>
// 目标度数
const target = 360;
// 初始度数
let now = 0;
// 步长
const speed = 10;
// 半径
const r = 200;
let t = setInterval(()=>{
if( now >= target ){
clearInterval(t);
}else{
// 创建元素
const box = document.createElement("div");
// 设置基本样式
box.className = "box";
// 插入body
document.body.appendChild(box);
// 改变角度
now += speed;
// 根据角度计算当前元素位置
box.style.left = r * Math.cos( Math.PI / 180 * now ) + 200 + "px";
box.style.top = r * Math.sin( Math.PI / 180 * now ) + 200 + "px";
}
}, 30)
</script>
</html>
六、缓冲运动
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 100px;height: 100px;background: red;position: absolute;left:0px;top:0;}
.line{width: 1px;background: black;position: absolute;left:400px;top:0;height: 400px;}
</style>
</head>
<body>
<div class="box"></div>
<div class="line"></div>
</body>
<script>
const box = document.querySelector(".box");
let t;
const target = 300;
box.onclick = function(){
if(t) return;
t = setInterval(()=>{
// 步长为:剩下距离n分之一
let speed = (target - box.offsetLeft) / 10;
// 根据正负值,选择向上或向下取整
speed = speed < 0 ? Math.floor(speed) : Math.ceil(speed);
// 因为缓冲运动,速度越来越慢,最后必然是1像素1像素的往前走,绝对可以正正好好到目标
if(target === box.offsetLeft){
clearInterval(t);
}else{
box.style.left = box.offsetLeft + speed + "px";
}
}, 30);
}
// 封装缓冲运动:任意元素任意属性运动到任意目标
</script>
</html>
七、缓冲运动封装
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box1{width: 100px;height: 100px;background: red;position: absolute;left:0px;top:0;}
.box2{width: 100px;height: 100px;background: green;position: absolute;left:0px;top:110px;}
.line{width: 1px;background: black;position: absolute;left:400px;top:0;height: 400px;}
</style>
</head>
<body>
<div class="box1"></div>
<div class="box2"></div>
<div class="line"></div>
</body>
<script>
const box1 = document.querySelector(".box1");
const box2 = document.querySelector(".box2");
box1.onmouseover = function(){
move(box1, "left", 300);
}
box2.onmouseover = function(){
move(box2, "width", 400);
}
// 封装缓冲运动:任意元素任意属性运动到任意目标
// 功能:多元素单个不同属性任意目标缓冲运动
function move(ele, attr, target){
if(ele.t) return;
// 将每个计时器的返回值都保存到当前元素身上,每个元素都需要有一个独立的计时器控制
ele.t = setInterval(()=>{
// 获取要操作的属性的当前值
let now = parseInt(getStyle(ele, attr));
let speed = (target - now) / 10;
speed = speed < 0 ? Math.floor(speed) : Math.ceil(speed);
if(target === now){
clearInterval(ele.t);
ele.t = undefined;
}else{
ele.style[attr] = now + speed + "px";
}
}, 30);
}
function getStyle(ele, attr){
if(ele.currentStyle){
return ele.currentStyle[attr];
}else{
return getComputedStyle(ele)[attr];
}
}
</script>
</html>
八、缓冲运动封装2.0
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box1{width: 100px;height: 100px;background: red;position: absolute;left:0px;top:0;}
.line{width: 1px;background: black;position: absolute;left:400px;top:0;height: 400px;}
</style>
</head>
<body>
<div class="box1"></div>
<div class="line"></div>
</body>
<script>
const box1 = document.querySelector(".box1");
box1.onmouseover = function(){
move(box1, {
top: 3,
left: 300
});
}
// 封装缓冲运动:任意元素任意属性运动到任意目标
function move(ele, obj){
if(ele.t) return;
ele.t = setInterval(()=>{
// 假设:当前运动,默认在目标,那么需要立即清除计时器
let state = true;
// 遍历属性所在对象,获取到属性和对应目标
for(let key in obj){
let now = parseInt(getStyle(ele, key));
let speed = (obj[key] - now) / 10;
speed = speed < 0 ? Math.floor(speed) : Math.ceil(speed);
ele.style[key] = now + speed + "px";
// 只要有一个属性没到目标,一定不能清除计时器,阻止计时器的清除
if(now !== obj[key]){
state = false;
}
}
// 检测是否阻止了计时器的清除,如果没有阻止清除,可以清除计时器
if(state){
clearInterval(ele.t);
}
}, 30);
}
// 问题:只要有一个属性到目标了,计时器就被清除了
// 解决:
// 有一个属性到目标了,计时器不一定被清除
// 假设:当前运动,默认在目标,那么需要立即清除计时器
// 验证假设:只要有一个属性没到目标,一定不能清除计时器,阻止计时器的清除
// 检测是否阻止了计时器的清除:如果没有阻止清除,可以清除计时器
function getStyle(ele, attr){
if(ele.currentStyle){
return ele.currentStyle[attr];
}else{
return getComputedStyle(ele)[attr];
}
}
</script>
</html>
九、缓冲运动封装3.0
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box1{width: 100px;height: 100px;background: red;position: absolute;left:0px;top:0;}
.line{width: 1px;background: black;position: absolute;left:400px;top:0;height: 400px;}
</style>
</head>
<body>
<div class="box1"></div>
<div class="line"></div>
</body>
<script>
const box1 = document.querySelector(".box1");
box1.onmouseover = function(){
// move(box1, {
// left: 300
// }, function(){
// move(box1, {
// top: 300
// }, function(){
// move(box1, {
// left:0
// }, function(){
// move(box1, {
// top:0
// })
// })
// })
// });
move(box1,{left:400},()=>{
box1.style.background = "yellow"
})
}
// 参数1:要操作的元素
// 参数2:要操作的属性和目标,对象格式
// 参数3:动画结束后要执行的功能,可传
function move(ele, obj, cb){
if(ele.t) return;
ele.t = setInterval(()=>{
let state = true;
for(let key in obj){
let now = parseInt(getStyle(ele, key));
let speed = (obj[key] - now) / 10;
speed = speed < 0 ? Math.floor(speed) : Math.ceil(speed);
ele.style[key] = now + speed + "px";
if(now !== obj[key]){
state = false;
}
}
if(state){
clearInterval(ele.t);
ele.t = undefined;
// 动画结束后,执行传入的功能
cb && cb();
}
}, 30);
}
function getStyle(ele, attr){
if(ele.currentStyle){
return ele.currentStyle[attr];
}else{
return getComputedStyle(ele)[attr];
}
}
</script>
</html>
十、流氓悬浮框
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{width: 100px;height: 40px;line-height: 40px;text-align: center;background: #ccc;position: absolute; left:0; top:200px;}
</style>
</head>
<body>
<div class="box">悬浮框</div>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<p>段落</p>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<h1>一级菜单</h1>
<p>段落</p>
<h1>一级菜单</h1>
</body>
<script src="../move.js"></script>
<script>
const box = document.querySelector(".box");
const t = box.offsetTop;
onscroll = function(){
move(box, {top: document.documentElement.scrollTop + t})
}
</script>
</html>
十一、折叠菜单栏
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.box{position: absolute;left:-300px;top:400px;width: 300px;height: 50px;}
.box .xbox{width: 300px;height: 50px;background: #ccc;}
.box .ybox{position: absolute;width: 300px;height: 0px;background: #aaa;top: 0px;overflow: hidden;}
.box span{width: 20px;height: 50px;background: #666;position: absolute;text-align: center;line-height: 50px;color: #fff;right: -20px;}
.box .ybox .close{float: right;}
</style>
</head>
<body>
<div class="box">
<span> > </span>
<div class="xbox">这是横向菜单这是横向菜单这是横向菜单这是横向菜单这是横向菜单</div>
<div class="ybox">
<input type="button" value="x" class="close">
这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单这是竖向菜单
</div>
</div>
</body>
<script>
const box = document.querySelector(".box");
const ybox = document.querySelector(".box .ybox");
const span = document.querySelector('.box span');
const close = document.querySelector('.box .ybox .close');
span.onclick = function(){
move(box, {left:0}, ()=>{
move(ybox, {top: -300, height: 300});
})
}
close.onclick = function(){
move(ybox, {top: 0, height: 0}, ()=>{
move(box, {left: -300});
})
}
function move(ele, obj, cb){
if(ele.t) return;
ele.t = setInterval(()=>{
let state = true;
for(let key in obj){
let now = parseInt(getStyle(ele, key));
let speed = (obj[key] - now) / 10;
speed = speed < 0 ? Math.floor(speed) : Math.ceil(speed);
ele.style[key] = now + speed + "px";
if(now !== obj[key]) state = false;
}
if(state){
clearInterval(ele.t);
ele.t = undefined;
cb && cb();
}
}, 30);
}
function getStyle(ele, attr){
if(ele.currentStyle){
return ele.currentStyle[attr];
}else{
return getComputedStyle(ele)[attr];
}
}
</script>
</html>
十二、轮播图
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Document</title>
<style>
.banner{width: 300px;height:90px;margin: 0 auto;position: relative;}
.banner .imgbox{position: absolute;left:0px;}
.banner .imgbox img{width: 300px;height: 90px;float: left;}
.banner .btns input{position: absolute;width: 30px;height:30px;top: 30px;}
.banner .btns .left{left:0;}
.banner .btns .right{right:0;}
</style>
</head>
<body>
<div class="banner">
<div class="imgbox">
</div>
<div class="btns">
<input type="button" value="<" class="left">
<input type="button" value=">" class="right">
</div>
</div>
</body>
<script src="../move.js"></script>
<script>
// 模拟后台给的图片数据
const bannerData = [{
name:"图1",
src:"../imgs/banner1.jpg"
},{
name:"图2",
src:"../imgs/banner2.jpg"
},{
name:"图3",
src:"../imgs/banner3.jpg"
},{
name:"图4",
src:"../imgs/banner4.jpg"
},{
name:"图5",
src:"../imgs/banner5.jpg"
}];
const imgbox = document.querySelector(".imgbox");
const left = document.querySelector(".left");
const right = document.querySelector(".right");
// 遍历解析数据,拼接页面结构字符
let str = "";
bannerData.forEach(val=>{
str += `<img src="${val.src}" title="${val.name}" alt="${val.name}">`;
})
// 设置到指定容器
imgbox.innerHTML = str;
// 获取所有图片元素
const imgs = document.querySelectorAll(".imgbox img");
// 设置imgbox的初始宽度,保证所有图片在一行显示
imgbox.style.width = imgs.length * imgs[0].offsetWidth + "px";
// 设置默认索引
let index = 0;
imgbox.style.left = -index * imgs[0].offsetWidth + "px";
left.onclick = function(){
// 计算索引
if(index === 0){
index = imgs.length-1
}else{
index--;
}
// 根据索引重新计算imgbox的位置,通过动画函数设置
move(imgbox, {
left: -index * imgs[0].offsetWidth
})
}
// right.onclick = function(){}
</script>
</html>
十三、复习及巩固
1、复习
- ES6新增了哪些声明关键字,分别用来声明什么内容
- let,const声明变量
- class声明类
- import,export声明模块
- 解构赋值的使用规则是什么
- 对象的解构:按照键名一一对应,不存在的键为undefined
- {} = obj
- 数组的解构:按照从左向右的顺序一一对应,不存在的位置为undefined
- [] = arr
- 箭头函数有什么特点
- 没有自己的this,使用外层函数的this
- 不能被new执行
- 语法极简
- 有且只有一个形参,可以省略小括号
- 有且直接返回值,可以省略花括号和return
- 不能使用arguments
- 事件委托的封装
父元素.onclick = eventProxy(aLi, function(){
// 触发事件的子元素
console.log(this)
})
function eventProxy(child, cb){
return (eve)=>{
const e = eve || window.event;
const target = e.target || e.srcElement;
[...child].forEach(val=>{
if(val === target){
cb.call(target);
}
})
}
}
- 正则中的量词有哪些,分别表示什么范围
- *:0个或以上
- +:1个或以上
- ?:0个或1个
- {n}:n个
- {n,}:至少n个
- {n,m}:至少那个,最多m个
- 对象的分类
- 宿主:寄生于平台的对象:document,window
- 内建:ECMAScript提供的对象:Math,Date,String,Number,RegExp
- 自定义:用户自己创建的对象
- this的常见指向
- 默认绑定:没有明确隶属对象的函数,直接执行,window或undefined
- 隐式绑定:有明确隶属对象的函数,被对象执行,该对象
- 显示绑定:使用函数的方法(call,apply,bind)强行改变,改变之后的值
- new绑定:使用new关键字执行函数,new出来的对象
- 如何开启/关闭,计时器/延时器
- 开启计时器:let t1 = setInterval(()=>{}, 1000)
- 关闭计时器:clearInterval(t1);
- 开启延时器:let t2 = setTimeout(()=>{}, 1000)
- 关闭延时器:clearTimeout(t2);
- 如何对数值进行向上或向下取整
- 向上:Math.ceil( n )
- 向下:Math.floor( n )
- parseInt( n )
2、ES6新增
- 声明关键字
- let 和 const
- 不存在声明提升
- 不允许重复声明
- 存在块级作用域
- 全局变量不会绑定到window
- 存在暂时性死区
- const声明的变量不允许修改地址
- 箭头函数
- 解构赋值
- 展开运算符
- …数据
- 对象的展开:只能在能接收键值对的位置展开
- 数组的展开:只能在能接收多个数据的位置展开
- 字符的扩展
- 字符串模板:`
- 支持回车换行
- 支持使用${}解析变量
- 字符的方法
- includes
- startsWith
- endsWith
- repeat
- codePointAt
- String.fromCodePoint
- 默认值
- 解构赋值时,左侧可以直接使用=号设置默认值
- 函数传参式,形参可以直接使用=号设置默认值
- 对象的简写
- 属性:属性名和属性值所在的变量名重名,可以简写成一个单词
- 方法:可以直接省略冒号和function关键字
- Symbol类型
- Symbol(),唯一性,任何两个都不相等
- 用来做唯一的状态标记
- Set和Map
- Set:无重复的多个数据
- new Set([v1,v2,v3,v4,v5])
- Map:无重复的值值对
- new Map([[“n1”,“v1”],[“n2”,“v2”],[“n3”,“v3”]])
3、DOM动画
- 基本动画(匀速运动)
- 进度条
- 重力运动(加速运动)
- 跳动的小球
- 圆周运动
- 画圆
- 缓冲运动(减速运动)
- 各种案例