banner
banner
banner
NEWS LETTER

React-动画与定时器案例

Scroll down

以下基于react hooks + tailwindcss + scss

文字雨

思路解析

  • 先将云层在网页上定位好,并拿到云层的dom
  • 设置一个随机的文字数组,让目的文字一个一个落下
  • 定义随机尺寸、随机left和随机的过渡时间
  • 创建一个div并向里面添加随机的文字数组,设置该div的样式、字体、动画过渡时间,
  • 向云层的dom添加这个div
  • 使用定时器来定时清除dom
  • 定时调用这个文字雨函数

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
const [textlist, setTextList] = useState([])
const [left, setLeft] = useState(0)
const [size, setSize] = useState(0)
const [duration, setDuration] = useState(0)
const randomText = () => {
const text = "云破星出之夜,我对星低喃:吞噬我。"
const letter = text[Math.floor(Math.random() * text.length)]
return letter
}
// 虽然不丝滑,但基本无问题
useEffect(() => {
const timer = setInterval(()=> {
const temp = {
value:randomText(),
left: Math.floor(Math.random() * 310),
size: Math.random() * 1.5,
duration: Math.random() * 10
}
list=[temp, ...list]
setTextList(list)
},900)
return () => {
clearInterval(timer)
}
}, [])
return (
<div className="relative h-[560px]">
<div id="cloud" className="relative top-[70px] w-[300px] h-[300px] bg-cover z-30" style={{ backgroundImage: 'url(/asset/image/cloud.png)'}}></div>
{textlist.length && textlist.map((item,index) => {
return <div key={index} style={{fontSize:`${0.5 + item.size}em`, left: `${item.left}px`, animationDuration: `${1 + item.duration}s`}} className="text">{item.value}</div>
})}
</div>
)
//scss
.text {
position: absolute;
line-height: 20px;
color: #fff;
text-shadow: 0 0 5px #fff,
0 0 15px #fff
0 0 30px #fff;
animation: rain 1s linear infinite;
}
@keyframes rain {
0% {
transform: translateY(0) scale(1);
}
80% {
transform: translateY(230px) scale(1);
}
// 使雨点落到最下面时回弹10px 后变大并透明消失
100% {
transform: translateY(220px) scale(1.3);
opacity: 0;
}
}

3D分层图(CSS)

思路解析

  • 先整体在y轴上偏移
  • 将图片放置在同一容器并设置在x轴偏移45度并设置变形的原点为top
  • 将第一张图片设置为相对定位,其余两张设置为绝对定位并且top和left为0
  • 最重要的是在图片的容器中添加hover样式,并给每个图片的设置偏移位置和透明度(相同类不同子元素)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 3D分层图
<div className="flex flex-col skew-y-[-20deg] mt-[100px] ml-[50px] relative cursor-pointer">
<div className="imgContainer skew-x-[45deg] origin-top grayscale">
<img alt="分层一" src="/asset/image/bg-2.jpg" className="relative h-[200px]" />
<img alt="分层一" src="/asset/image/bg-2.jpg" className="absolute top-0 left-0 h-[200px]" />
<img alt="分层一" src="/asset/image/bg-2.jpg" className="absolute top-0 left-0 h-[200px]" />
</div>
<div className="pl-[200px] text-4xl text-white">CSS黑白3D分层图像</div>
<section className="pl-[260px] text-white skew-x-[45deg]">《二泉映月》的音符如泉眼汩汩洇漫,我们知道那流浪着的该是一种无奈;《病中吟》的曲调如泪水缓缓流出,我们知道那流浪着的分明是一种悲凉;《良宵》的节拍如思念浓浓笼罩,我们知道那流浪着的更是一种彻骨的沧桑。不是二胡的流浪、音乐的流浪,那样的流浪是一个灵魂的流浪、一方土地的流浪,那样的流浪是一个时代的流浪、一个民族的流浪。</section>
<div className="ml-[320px] text-lg text-white skew-x-[-10deg] border-double border-[6px] w-fit px-[4px] hover:border-[10px]" onClick={() => console.log('You rose to the bait.')}>更多内容</div>
</div>
// 3d图层scss
.imgContainer:hover {
& img:nth-child(3) {
transform: translate(100px, -100px);
}
& img:nth-child(2) {
transform: translate(50px, -50px);
opacity: 0.5;
}
& img:nth-child(1) {
transform: translate(0px, 0px);
opacity: 0.2;
}
}

3D立体图片旋转

解析思路

  • 先构造圆的虚拟线,定原点,分析每一个div落在上面的位置,并结合三角函数来落实div的位置
  • 通过增加弧度使div缓缓地向下移动,形成旋转的轨迹。

关键点

解决方式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
import React from 'react'
import { memo, useState, useEffect } from 'react'
import './index.scss'
// 缺陷:speed数据异常,move调用会多次调用
const Log = memo((props) => {
const { widthOuter, heightOuter, recyleNum } = props

// 图片div 的数据信息
const [box,setBox] = useState([])
// 用来作触发页面渲染的标识
const [flag, setFlag] = useState(0)
// 标记宽度为0 的情况
const [count, setCount] = useState(0)
// 弧度数: 把圆平分成八个部分的每一个对应的角度数
const rad = (360/8)*Math.PI/180
// 图片div的外围容器的大小
const [outerWidth, setOuterWidth] = useState(widthOuter ? widthOuter : 600)
const [outerHeight, setOuterHeight] = useState(heightOuter ? heightOuter : 600)
// 圆的半径
const radius = outerWidth/ 2
// 外围容器的top值 = Math.cos(rad * index) * 圆的半径
// 外围容器的left值 = Math.sin(rad * index) * 圆的半径 ==== 求每一个圆心角的直角边
// 生成原点的位置(虚拟的原点)
const dotLeft = outerWidth /2
const dotTop = outerHeight /2
// 生成图片div 的位置信息
if(!box.length) {
for (let i = 0; i< 8; i++) {
box.push({
img: `/asset/image/box${i + 1}.jpg`,
left: Math.floor(Math.sin(rad * i) * radius + dotLeft) - 140 / 2,
top: Math.floor(Math.cos(rad * i) * radius + dotTop) - 200 / 2
})
}
}
// 设置动起来的速度=== 设置角度
const [speed,setSpeed] = useState(0)
// 让图片div动起来

const move = () => {
setSpeed(speed < 360 ? speed + 2 : 2)
// 运动的弧度数
const distance = (speed * Math.PI) / 180
const _box = box.map((item, index) => {
item.img = `/asset/image/box${index + 1}.jpg`,
item.left = Math.sin(rad * index + distance) * radius + dotLeft - 140 / 2,
item.top = Math.cos(rad * index + distance) * radius + dotTop - 200 / 2
return item
})
setBox(_box)
}

// 调用定时器
useEffect(() => {
const timer = setInterval(() => {
move()
setFlag(flag + 1)
}, 100)
return () => {
clearInterval(timer)
}
}, [flag])
// 缩放布局
const changeWeel = (e) => {
const Y = e.deltaY
// 向下滚,放大
if(Y < 0) {
setOuterWidth(outerWidth + 10)
setOuterHeight(outerHeight + 10)
}else {
// 向上滚,缩小
setOuterWidth(outerWidth - 10)
setOuterHeight(outerHeight - 10)
// 用来标记宽高为0
if(outerHeight === 0) {
setCount(count+1)
}
// 只有当标记不为0并且宽度小于默认的宽度时,让宽度回到默认宽度,否则清除标记,重新判断
if(count && outerWidth< 600) {
setOuterWidth(Math.abs(outerWidth + 10))
setOuterHeight(Math.abs(outerHeight + 10))
}else if(count && outerWidth >=600) {
setCount(0)
}
}
}
return (
// 可移动可旋转可收缩有倒影的圆形立体图片展览
<div className='relative w-full h-[900px] floorboard bg-black flex justify-center items-center' onWheel={(e) => changeWeel(e)}>
<div className='relative bg-transparent z-30 circle-wrap' style={{ width: `${outerWidth}px`, height: `${outerHeight}px` }} >
{box.map((item, index) => {
return (
<img
key={index}
src={item.img}
alt=''
style={{ left: `${item.left}px`, top: `${item.top}px`, }}
/>
)
})}
<div className='absolute ground text-white'>
3D Tiktok Carousel
</div>
</div>
</div>
)
})
export default Log
// scss
.floorboard {
overflow: hidden;
.circle-wrap {
display: flex;
justify-content: center;
align-items: center;
perspective: 1100px;
transform-style: preserve-3d;

img {
position: absolute;
height: 200px;
width: 140px;
box-shadow: 0 0 8px #fff;
transform-origin: center;
-webkit-box-reflect: below 10px
linear-gradient(transparent, transparent, #0005); // 倒影渐变
}
img:hover {
box-shadow: 0 0 15px #fffd;
-webkit-box-reflect: below 10px
linear-gradient(transparent, transparent, #0007);
}
}
.ground {
left: calc((100% - 144px) / 2);
top: calc((100% - 25px) / 2);
background: -webkit-radial-gradient(
center center,
farthest-side,
#9993,
transparent
);
animation: spinRevert 10s infinite;
transform: rotate(0deg) scale(1.05, 0.45) translate(0px, 0px)
skew(-18deg, 0deg);
-webkit-transform: rotate(0deg) scale(1.05, 0.45) translate(0px, 0px)
skew(-18deg, 0deg);
z-index: -1;
}
}

@keyframes spinRevert {
from {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg);
}
to {
-webkit-transform: rotateZ(0deg);
transform: rotateZ(0deg);
}
}

遇到的问题

数组已经改变,但页面没有渲染

  • 使用useEffect来触发页面渲染,数组使用useState来赋值

move函数每次调用时,会同时触发多次,并且speed的数值异常

  • 破解的关键在于定时器,需要清除定时器
  • 在useEffect中使用无副作用,即无依赖就不会多次触发。接着将定时器赋给一个变量,然后返回返回清除定时器。定时器中要存在一个能触发的变量,即使用useState的变量,无关值,只用于触发渲染页面
其他文章
cover
React-React与定时器
  • 24/11/01
  • 09:41
  • React
cover
React-面试题
  • 24/11/01
  • 09:41
  • React
目录导航 置顶
  1. 1. 文字雨
    1. 1.1. 思路解析
    2. 1.2. 示例
  2. 2. 3D分层图(CSS)
    1. 2.1. 思路解析
    2. 2.2. 示例
  3. 3. 3D立体图片旋转
    1. 3.1. 解析思路
    2. 3.2. 关键点
    3. 3.3. 解决方式:
    4. 3.4. 遇到的问题
请输入关键词进行搜索