特效篇--图片环绕

图片环绕
2017年快要结束了,来点特效的东西作为2017年博客的结束篇吧,其实特效这东西看看就好了,实际用处不是特别的大,不过效果什么的还是挺酷的。

本文介绍的是图片环绕,散列切换如何完成的,一共三个切换效果,先看看效果吧:

当然博客展示的面积不够:请点这里查看全部效果链接
废话不多说直接上代码了:
html:

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
<div class="btnbox">
<button class="show btn">散列</button>
<button class="btn">横环装</button>
<button class="btn">竖环装</button>
<div class="pox"><label for="change">自动播放:</label><input id="change" type="checkbox"/>
&nbsp;速度:<input type="range" value="4" max="10" min="1" id="speed" value="1"></div>
</div>
<div class="wrap" id="wrap">
<!-- //photo平移旋转
<div class="photo photo-front"">
//photo-wrap负责翻转
<div class="photo-wrap">
<div class="side side-front">
<p class="imgs"><img src=""/></p>
<p class="caption"></p>
</div>
<div class="side side-back">
<p class="desc">
</p>
</div>
</div>
</div> -->
</div>

html还是挺简单的,btnbox就是控制图片排列和轮播的控制台,#wrap就是我们的放置容器了,注释的代码是大致的样式,我们用两层div包裹住文字和图片,第一层div(.photo),这个是负责整个块的移动的,第二层div(.photo-wrap)是负责自身的前后两面翻转的,.side-front负责前面的图片和文字显示,.side-back负责背面的文字显示,由于所有的图片内容都是根据一个json生成的,所以这里#wrap里面所有的内容都是通过js生成的,#wrap实际上不用放任何东西。
css:

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
.wrap {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
background: rgba(0, 0, 0, 0.8);
overflow: hidden;
perspective: 1800px;
}
.photo {
width: 260px;
height: 320px;
position: absolute;
z-index: 1;
box-shadow: 0 0 1px rgba(0, 0, 0, 0.01);
transition: all 0.5s;
left: 50%;
top: 50%;
margin: -160px 0 0 -130px;
}
.photo .side {
width: 100%;
height: 100%;
background: #eee;
position: absolute;
right: 0;
top: 0;
padding: 20px;
box-sizing: border-box;
backface-visibility: hidden;
}
.photo .side-front {
transform: rotateY(0deg);
}
.side-back {
transform: rotateY(180deg);
}
/*当前选中样式*/
.photo-center {
left: 50%;
top: 50%;
margin: -160px 0 0 -130px;
z-index: 999;
}
.photo-wrap {
width: 100%;
height: 100%;
position: absolute;
transform-style: preserve-3d;
transition: all 0.5s;
transform-origin: 0;
}
.photo-front .photo-wrap {
transform: rotateY(0deg) translate(0px, 0px);
}
.photo-back .photo-wrap {
transform: rotateY(180deg) translate(-260px, 0px);
}

css算是比较复杂吧,主要是涉及css3的内容太多了,不熟悉的去充充电吧,这里只列出了一部分css,稍微讲解一下,基础的就不说了。
正反翻转:.side是反面和正面的共同属性,.side-front是正面所以transform: rotateY(0deg);旋转了0度正对屏幕,.side-back是背面用transform: rotateY(180deg);旋转180度,背面对着屏幕,其中用到了backface-visibility: hidden;这个属性,这个属性让不面向屏幕的旋转元素背面影藏掉,即.side-back隐藏掉了,当翻转.photo-wrap时,.side-front就隐藏了,这就让.photo-wrap看起来就像真的有两面一样。
原地翻转动画:.photo-wrap沿着Y轴旋转时我们要让其沿着左边进行180度旋转,所以transform-origin的x轴一定要为0,旋转后.photo-wrap就向左边移动了它本身的宽度的距离,简单来说就像翻书一样,一页翻过去了就相当于移动了一页的距离。我们要让其原地翻转就要对其x轴的translate进行设置,让其向右边移动它本身的宽度的距离,所以设置translate(-260px, 0px),这样就相当于它没有进行移动了,看起来就像原地翻转一样。

比较难以理解的css就是这些了,开始正式写了。
工欲善其事必先利其器,首先我们先准备好要渲染的数据,组合一个json,复杂的东西懒得写,就写点简简单单的吧,放40张图片吧:

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
function data() {
var data = [];
for (var i = 1; i < 41; i++) {
data.push({
img: './waterfall/images/' + i + '.jpg',
caption: '第' + texthan(i) + '张',
desc: '描述' + texthan(i)
});
};
return data;
};
function texthan(n) {
var han = ['一', '二', '三', '四', '五', '六', '七', '八', '九', '十'];
if (n > 10) {
var s = Math.floor(n / 10) > 1 ? han[Math.floor(n / 10) - 1] + '十' : '十';
var g = n % 10 - 1 >= 0 ? han[n % 10 - 1] : '';
return s + g;
} else {
return han[n - 1];
}
}
//其实大致就是下面这个json,img为图片地址,caption是第几张图,desc是图片描述
[{img: "./waterfall/images/1.jpg", caption: "第一张", desc: "描述一"},{img: "./waterfall/images/2.jpg", caption: "第二张", desc: "描述二"},{img: "./waterfall/images/3.jpg", caption: "第三张", desc: "描述三"}]

好了,图片准备好了,开始写函数了,其他的东西懒得配置了,就俩个配置项,activeindex是当前选中的图片索引,state是轮播的类型:

1
2
3
4
5
function Surround(option) {
option = option || {};
this.activeindex = option.activeindex || 0;
this.state = option.state || 0;
}

老规矩,先来几个工具函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Surround.prototype = {
getdom: function (obj) {
var method = obj.substr(0, 1) == '.' ? 'getElementsByClassName' : 'getElementById';
return document[method](obj.substr(1));
},
getwh: function (obj) {
return {
w: obj.clientWidth,
h: obj.clientHeight
}
},
select: function (start, end) {
var num = end - start + 1;
return Math.floor(Math.random() * num + start);
},
time:function(data) {
if (this.activeindex > data.length - 1) {
this.activeindex = 0;
};
this.rsort(this.activeindex);
this.activeindex++;
}
}

getdom用于获取节点,getwh返回传入dom的宽高集合,select返回传入最大数和最小数之间的随机数,time负责轮播的切换。
其实轮播的原理很简单,从.photo中选出一个放在中间,其余的通过随机数给生成随机的top和left以及旋转角度,,点击中间的,调用的是翻转函数,点击其他的则把你点击的放中间,其他的再随机生成。移动动画什么的交给css3的transition完成。
原理说了,下面开始生成图片了,注下面的函数全写在Surround.prototype里的:

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
addPhotos: function (data) {
var lists = '';
var nav = '';
var navbox = document.createElement('div');
navbox.className = 'nav';
for (var i in data) {
var photobox =
`<div class="photo photo-front" id="photo_${i}">
<div class="photo-wrap">
<div class="side side-front">
<p class="imgs">
<img src="${data[i].img}" />
</p>
<p class="caption">${data[i].caption}</p>
</div>
<div class="side side-back">
<p class="desc">
${data[i].desc}
</p>
</div>
</div>
</div>`;
var navitem = `<span id="nav_${i}" class="i"></span>`
nav += navitem;
lists += photobox;
}
navbox.innerHTML = nav;
this.getdom('#wrap').innerHTML = lists;
this.getdom("#wrap").append(navbox);
var i = this.getdom(".nav")[0].getElementsByClassName('i');
var photo = this.getdom(".photo");
[].slice.call(i).forEach(function(el,index) {
el.onclick = function(){
this.turn(this.getdom('#photo_'+index));
}.bind(this);
},this);
[].slice.call(photo).forEach(function(el,index){
el.onclick = function(event){
this.turn(event.currentTarget);
}.bind(this);
},this);
this.rsort(this.activeindex);
}

我们先通过遍历data这个json创建出同数量的.photo和分页器.nav,然后分别给其添加点击事件调用turn()函数,然后调用rsort()函数传入this.activeindex,获得当前居中的索引。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
turn: function (elem) {
var cls = elem.className;
var n = elem.id.split('_')[1];
this.activeindex = Number(n);
if (!elem.classList.contains("photo-center")) {
return this.rsort(n);
}
if (elem.classList.contains('photo-front')) {
cls = cls.replace(/photo-front/, "photo-back");
this.getdom("#nav_" + n).classList.add('i_back');
} else {
cls = cls.replace(/photo-back/, "photo-front");
this.getdom("#nav_" + n).classList.remove('i_back');
};
return elem.className = cls;
}

turn()函数其实就是翻转函数,它根据传入函数的class判断是不是居中的.photo,是的话通过添加和移除class进行翻转,否者把this.activeindex改变为你点击的索引,调用rsort()函数传入this.activeindex
下面是主要的变化函数了:

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
rsort: function (n) {
var _photo = this.getdom('.photo');
var wrap = this.getwh(this.getdom('#wrap'));
var photo = this.getwh(this.getdom('.photo')[0]);
var photos = []
for (var i = 0; i < _photo.length; i++) {
_photo[i].className = 'photo photo-front';
_photo[i].style.left = '';
_photo[i].style.top = '';
_photo[i].style['transform'] = 'rotate(0deg) scale(1.3)';
photos.push(_photo[i]);
};
var photo_center = this.getdom('#photo_' + n);
photo_center.className += ' photo-center';
photo_center = photos.splice(n, 1)[0];
if (this.state === 0) {
this.separate(photos,function(photo){
photo.style.left = this.range().left.x + 'px';
photo.style.top = this.range().left.y + 'px';
photo.style['transform'] = 'rotate(' + this.select(-90, 90) + 'deg) scale(1)';
},function(photo){
photo.style.left = this.range().right.x + 'px';
photo.style.top = this.range().right.y + 'px';
photo.style['transform'] = 'rotate(' + this.select(-90, 90) + 'deg) scale(1)';
});
} else if (this.state === 1) {
for(var s in photos){
photos[s].style['transform'] = 'rotate(' + this.select(0, 360) +
'deg) scale(1) translate(500px)';
};
} else {
var r = 460;
for (var s = 0; s < photos.length; s++) {
var deg = this.select(0, 360);
var sinY = Math.sin(deg * Math.PI / 180) * r;
var cosX = Math.cos(deg * Math.PI / 180) * r;
photos[s].style.top = ((wrap.h / 2) + sinY) + 'px';
photos[s].style.left = ((wrap.w / 2) + cosX) + 'px';
photos[s].style.transform = 'rotate(' + (deg + 90) + 'deg)'
};
};
var navs = this.getdom('.i');
for (var i = 0; i < navs.length; i++) {
navs[i].className = 'i';
}
this.getdom("#nav_" + n).className += ' i_current';
}

rsort()函数先把所有的样式全部重置,然后把所有的.photo放入一个数组中,根据传入的索引把当前要居中的.photo取出来给其添加居中的class,
剩余的用for循环给其添加随机的样式。separate()函数的作用是把剩余.photo分成两部分,然后在回调函数里给其设置样式,range()函数的主要作用是将分成两部分的.photo给区分放置,一份放居中元素的左边,一份放居中元素的右边,写这个两个函数的原因是散列时有可能全部挤在一堆了,留下一堆空白的不好看,左右两边均分一下。

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
separate:function(photos,lfn,rfn){
var photos_left = photos.splice(0, Math.ceil(photos.length / 2));
var photos_right = photos;
for(s in photos_left){
var photo = photos_left[s];
lfn.apply(this,[photo]);
}
for(s in photos_right){
var photo = photos_right[s];
rfn.apply(this,[photo]);
}
}
range: function () {
var range = {
left: {
x: [],
y: []
},
right: {
x: [],
y: []
}
};
var wrap = this.getwh(this.getdom('#wrap'));
var photo = this.getwh(this.getdom('.photo')[0]);
range.left.x = this.select(-photo.w, (wrap.w - photo.w) / 2);
range.left.y = this.select(-photo.h, wrap.h);
range.right.x = this.select((wrap.w - photo.w) / 2, wrap.w);
range.right.y = this.select(-photo.h, wrap.h);
return range;
}

this.state为0的样式就基本完成了,至于this.state成圆环状的样式写法原因参考我时间–时钟篇,这里面有说明。
好了Surround函数基本完成了,下面我们开始调用,surround.getdom这个只是顺便用了Surround函数里面获取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
//轮播
var surround = new Surround();
surround.addPhotos(data());
surround.getdom("#change").onchange = function () {
if (this.checked) {
var speed =surround.getdom('#speed').value;
surround.getdom('#speed').onchange = function () {
speed = this.value;
clearInterval(timer);
timer = setInterval(function(){
surround.time(data());
}, speed * 500);
};
timer = setInterval(function(){
surround.time(data());
}, speed * 500);
} else {
clearInterval(timer);
};
};
//切换样式
btn();
function btn() {
for (var i = 0; i < surround.getdom('.btn').length; i++) {
(function (i) {
surround.getdom(".btn")[i].onclick = function () {
for (var j = 0; j < surround.getdom('.btn').length; j++) {
surround.getdom(".btn")[j].className = 'btn';
};
surround.getdom(".btn")[i].className = 'btn show';
surround.state = i;
surround.rsort(surround.activeindex);
};
})(i);
}
};

本文代码地址链接