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"/> 速度:<input type="range" value="4" max="10" min="1" id="speed" value="1"></div> </div> <div class="wrap" id="wrap"> <!-- <div class="photo photo-front""> <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]; } } [{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); } };
|
本文代码地址链接