特效篇–图片环绕

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

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

废话不多说直接上代码了:
html:

<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">

    <!--    //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:

 .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张图片吧:

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是轮播的类型:

function Surround(option) {
    option = option || {};
    this.activeindex = option.activeindex || 0;
    this.state = option.state || 0;
}

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

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里的:

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,获得当前居中的索引。

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
下面是主要的变化函数了:

 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给区分放置,一份放居中元素的左边,一份放居中元素的右边,写这个两个函数的原因是散列时有可能全部挤在一堆了,留下一堆空白的不好看,左右两边均分一下。

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的函数:

//轮播
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);
}
};
本文示例:链接
本文代码地址:链接

您也可能喜欢...

发表评论

电子邮件地址不会被公开。 必填项已用*标注