玩转3D交互
# 玩转3D交互
[TOC]
# 一、CSS立方体布局
# 1.1 CSS3D基本使用
/* perspective指定了观察者与 z=0 平面的距离,近大远小 */
perspective: 600px;
/* 在元素内-开启3d渲染模式 */
transform-style: preserve-3d;
/* 过渡时间 */
transition: 1s;
/* transform:rotate/translate/scale 变形 */
/* 沿Z轴向屏幕方向平移100px */
transform: translateZ(100px);
/* 以z轴为旋转轴,顺时针旋转90度 */
transform: rotateZ(90deg);
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
# 1.2 立方体
<div class="view">
<div class="box">
<span>1</span>
<span>2</span>
<span>3</span>
<span>4</span>
<span>5</span>
<span>6</span>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
.view {
perspective: 600px;
}
.box {
width: 200px;
height: 200px;
margin: 200px auto;
transform-style: preserve-3d;
position: relative;
transition: 2s;
}
.box span {
position: absolute;
width: 100%;
height: 100%;
border: 1px solid rgb(0, 0, 0);
background: rgb(224, 218, 218,0.2);
}
/* 这里rotateY()与translateZ()的顺序不能换 */
.box span:nth-child(1) {
transform: translateZ(100px);
}
.box span:nth-child(2) {
transform: rotateY(90deg) translateZ(100px);
}
.box span:nth-child(3) {
transform: rotateY(180deg) translateZ(100px);
}
.box span:nth-child(4) {
transform: rotateY(270deg) translateZ(100px);
}
.box span:nth-child(5) {
transform: rotateX(90deg) translateZ(100px);
}
.box span:nth-child(6) {
transform: rotateX(-90deg) translateZ(100px);
}
.box{
transform: rotateX(-30deg) rotateY(30deg);
}
.box:hover {
transform: rotateX(90deg) rotateY(90deg);
}
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
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
# 二、动态实现可复制布局
# 2.1 Document.createDocumentFragment()
创建一个新的空白的文档片段(
DocumentFragment
)。DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。
因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。因此,使用文档片段通常会带来更好的性能。
示例:创建主流Web浏览器的列表。
<ul id="ul"> </ul>
1
2var element = document.getElementById('ul'); // assuming ul exists var fragment = document.createDocumentFragment(); var browsers = ['Firefox', 'Chrome', 'Opera', 'Safari', 'Internet Explorer']; browsers.forEach(function(browser) { var li = document.createElement('li'); li.textContent = browser; fragment.appendChild(li); }); element.appendChild(fragment);
1
2
3
4
5
6
7
8
9
10
11
12
# 2.2 创建可复制的立方体
# 2.2.1 动态生成立方体i
<div class="view">
<div class="bigbox"></div>
</div>
<script>
// 表示文档中与指定的一组CSS选择器匹配的第一个元素的 html元素Element对象。
let bigbox=document.querySelector('.bigbox');
// 三循环,三层三行五列
let zSize=3,rows=3,ceils=5;
// 创建文档碎片
let fragment = document.createDocumentFragment();
for(let z=0;z<zSize;z++){
for(let y=0;y<rows;y++){
for(let x=0;x<ceils;x++){
let box=document.createElement('div');
// 给DOM节点添加class
box.classList.add('box');
box.innerHTML=`
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
<span></span>
`;
// 创建了节点后,不要忘了放在dom节点上
// 但是直接放在for循环里很耗性能,这个时候就可以使用文档碎片
fragment.appendChild(box);
}
}
}
// 插入到dom节点
bigbox.appendChild(fragment);
</script>
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
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
# 2.2.2 确定立方体的位置
近大远小,离中心位越远间距越大。
z+1是为了避免出现z=0的情况。
以中心点为圆心,向四周发散。
// 批量修改样式
box.style.cssText=`
top:${boxh*y}px;
left:${boxw*x}px;
transform:translateX(${(x-Math.floor(ceils/2))*200*(z+1)}px)
translateY(${(y-Math.floor(rows/2))*200*(z+1)}px)
translateZ(${z*200}px);
`;
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# 三、运动框架mTween.js
- mTween.js
- css(el,'styleName',val); 设置样式
- css(el,'styleName'); 读取样式
- mTween({
el, //运动元素
attrs, //修改样式值
duration //运动时间
fx:'easeOutStrong' //运动形式
cb(){
//回调函数
}
})
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
function mTween(props) {
// 操作的对象
var el = props.el;
if (el.mTween) return;
// 运动变化时间
var duration = props.duration || 400,
fx = props.fx || 'easeOut',
cb = props.cb,
// 要改变的元素样式
attrs = props.attrs || {};
s = props.s
var beginData = {},
changeData = {};
var maxDis = 0;
for (var key in attrs) {
beginData[key] = css(el, key);
changeData[key] = attrs[key] - beginData[key];
maxDis = Math.max(Math.abs(changeData[key]), maxDis);
}
if (typeof duration == "object") {
durationPorps = duration;
duration = durationPorps.multiple !== undefined ? maxDis * durationPorps.multiple : maxDis * 1.2;
if (durationPorps.min !== undefined) {
duration = duration < durationPorps.min ? durationPorps.min : duration;
}
if (durationPorps.max !== undefined) {
duration = duration > durationPorps.max ? durationPorps.max : duration;
}
}
// 两个参数获取属性,一个参数设置属性
function css(ele, attr, val) {
if (typeof attr === 'string' && typeof val === 'undefined') {
if (css3Attr.indexOf(attr) !== -1) {
return transform(ele, attr);
}
var ret = getComputedStyle(ele)[attr];
return normalAttr.indexOf(attr) !== -1 ? parseFloat(ret) : ret * 1 === ret * 1 ? ret * 1 : ret;
}
function setAttr(attr, val) {
if (css3Attr.indexOf(attr) !== -1) {
return transform(ele, attr, val);
}
if (normalAttr.indexOf(attr) !== -1) {
ele.style[attr] = val ? val + 'px' : val;
} else {
ele.style[attr] = val;
}
}
// 批量设置
if (typeof attr === 'object') {
for (var key in attr) {
setAttr(key, attr[key]);
}
return;
}
setAttr(attr, val);
}
function transform(el, attr, val) {
el._transform = el._transform || {};
if (typeof val === 'undefined') {
return el._transform[attr];
}
el._transform[attr] = val;
var str = '';
for (var key in el._transform) {
var value = el._transform[key];
switch (key) {
case 'translateX':
case 'translateY':
case 'translateZ':
str += `${key}(${value}px) `;
break;
case 'rotate':
case 'rotateX':
case 'rotateY':
case 'skewX':
case 'skewY':
str += `${key}(${value}deg) `;
break;
default:
str += `${key}(${value}) `;
}
}
el.style.transform = str.trim();
}
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
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
# 3.1 入场动画
每层设置不一样的背景图
.box[data-z="0"] span:nth-child(1){
background: url(img/bg1.png);
}
.box[data-z="1"] span:nth-child(1){
background: url(img/bg2.png);
}
.box[data-z="2"] span:nth-child(1){
background: url(img/1.jpg);
/* 限制图片大小 */
background-size: 1000px 600px;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
三层for循环内需要做的改动
css(box, 'translateX', (x - Math.floor(ceils / 2)) * 200 * (z + 1));
css(box, 'translateY', (y - Math.floor(rows / 2)) * 200 * (z + 1));
css(box, 'translateZ', z * 200);
//设置初始值
css(box,'rotateY',0);
// 操作自定义属性
// 方便for循环外能获取需要的值
box.dataset.x = x;
box.dataset.y = y;
box.dataset.z = z;
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
// 入场动画
let boxs = document.querySelectorAll('.box');
// 用来控制总动画的时长
let times = 3000;
boxs.forEach(item => {
// 每一层延迟的时间不一样
let z = item.dataset.z;
// random()得到的伪随机数是[0,1)
let dely = Math.random() * 500 + z * 700;
setTimeout(() => {
// 显示box
item.classList.add('show');
// el:运动元素
// attrs:运动样式
mTween({
el:item,
attrs:{
translateX:0,
translateY:0,
// 避免因为box凸出而导致了间隙
translateZ:-100
},
duration:times-dely
});
}, dely);
});
// 删除入场结束后多余的dom元素
setTimeout(()=>{
boxs.forEach(item=>{
if(item.dataset.z<2){
item.remove();
}
})
},times);
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
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
# 四、幻灯片效果实现原理
// 获取事件元素
let btns = document.querySelectorAll('.btn button');
// 设置开关
// 判断运动是否结束
let isPlay = false;
// 存储运动形式
let fnArr = [];
// 存储当前运动是第now个
let now = 0;
btns[0].onclick = function () {
// 如果isPlay是true,阻止点击事件执行
if (isPlay) return;
isPlay = true;
// fnArr.length在所有变量提升后就会变为3
fnArr[now % fnArr.length](90);
now++;
};
btns[1].onclick = function () {
if (isPlay) return;
isPlay = true;
fnArr[now % fnArr.length](-90);
now++;
};
// 运动形式:统一方向翻转
// 为保证每个box的独立性,设置每个box翻转的起始时间不一致
fnArr[0] = function (deg) {
let times = 1000;
boxs.forEach(item => {
let dely = Math.random() * 400;
setTimeout(() => {
mTween({
el: item,
attrs: {
rotateY: css(item, 'rotateY') + deg
},
duration: times - dely
});
}, dely)
});
// 运动结束后,将开关设置为false
setTimeout(() => {
isPlay = false;
}, times)
};
// 运动形式:向前平移+旋转+向后平移
fnArr[1] = function (deg) {
let times = 2300;
boxs.forEach(item => {
let x = item.dataset.x;
let y = item.dataset.y;
// 从左上角到右下角运动
// x+y从0到max变化
let dely = x * 80 + y * 80;
setTimeout(() => {
mTween({
el: item,
attrs: {
translateZ: css(item, 'translateZ') + 100
},
duration: 600,
//当前运动完毕时执行(回调函数,链式运动必备)
cb: () => {
mTween({
el: item,
attrs: {
rotateY: css(item, 'rotateY') + deg
},
duration: 600,
//当前运动完毕时执行
cb: () => {
mTween({
el: item,
attrs: {
translateZ: css(item,
'translateZ') -
100
},
duration: 600,
});
}
});
}
});
}, dely);
});
setTimeout(() => {
isPlay = false;
}, times);
};
// 运动形式: 偶数块向前移、奇数块向后移+翻转
fnArr[2] = function (deg) {
let times = 2200;
boxs.forEach(item => {
let x = parseInt(item.dataset.x);
let y = parseInt(item.dataset.y);
let dely = Math.random() * 400;
//判断方向
let dir = 1; // 1 -1
if (x % 2) {
dir = y % 2 ? -1 : 1;
} else {
dir = y % 2 ? 1 : -1;
}
setTimeout(() => {
mTween({
el: item,
attrs: {
translateZ: css(item, 'translateZ') + 50 * dir
},
duration: 600,
//当前运动完毕时执行
cb: () => {
mTween({
el: item,
attrs: {
rotateY: css(item, 'rotateY') + deg
},
duration: 600,
//当前运动完毕时执行
cb: () => {
mTween({
el: item,
attrs: {
translateZ: css(item,
'translateZ') -
50 * dir
},
duration: 600,
});
}
});
}
});
}, dely);
});
setTimeout(() => {
isPlay = false;
}, times);
};
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
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