欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

ES6实现图片切换特效(图片墙效果)

程序员文章站 2022-04-14 16:09:22
按照国际惯例先放效果图 贴代码: index.html index

按照国际惯例先放效果图

ES6实现图片切换特效(图片墙效果)

贴代码:

index.html

<!doctype html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>index</title>
    <link rel="stylesheet" href="index.css">
</head>
<body>
    <div id="wrap">
        <!-- <div class="img_container">
            <ul class="img_classify">
                <li class="img_classify_type_btn img_classify_type_btn_active">类别1</li>
                <li class="img_classify_type_btn">类别2</li>
            </ul>
            <div class="img_pic_container">
                <figure>
                    <img src="images/1.jpg" alt="1">
                    <figcaption>title</figcaption>
                </figure>
            </div>
        </div> -->
    </div>
    <!-- 遮罩层,预览时出现大图 -->
    <!-- <div class="img_overlay">
        <div class="img_overlay_prevbtn"></div>
        <div class="img_overlay_nextbtn"></div>
        <img src="images/1.jpg" alt="1">
    </div> -->

    <script src="index.js"></script>
    <script src="data.js"></script>
    <script>
        const img=new $img({
            data,
            inittype:"javascript",//默认显示的分类
            outwrap:"#wrap"//所有dom挂载点
        });
    </script>
</body>
</html>

index.css

*{
    margin:0;
    padding:0;
}
body{ 
    background: #fafafa;
    background: url('images/bg.png')
}
li{
    list-style:none;
}
a{
    text-decoration: none;
}
::-webkit-scrollbar {
    display: none;
}
#wrap{  
    width: 1065px;
    margin: 0 auto;
    padding: 30px;
    background: rgb(255, 255, 255);
    border-radius: 2px;
    margin-top: 100px;
}
.img_container{
    font-size: 10px;
}
.img_classify_type_btn{
    display: inline-block;
    padding: .2em 1em;
    font-size: 1.6em;
    margin-right: 10px;
    cursor: pointer;
    border: 1px solid #e95a44;
    outline: none;
    color: #e95a44;
    transition: all .4s;
    user-select: none;/*文字不允许用户选中*/
    border-radius: 2px;
}
.img_classify_type_btn_active{
    background: #e95a44;
    color: #fff;
}
.img_pic_container{
    position: relative;
    margin-top: 30px;
    width: 1005px;
    display: flex;
    flex-wrap: wrap;
    transition: all .6s cubic-bezier(0.77, 0, 0.175, 1);/*动画效果*/
}
.img_pic_container figure{
    width: 240px;
    height: 140px;
    position: absolute;
    transition: all .6s cubic-bezier(0.77, 0, 0.175, 1);
    transform: scale(0, 0);
    opacity: 0;
    overflow: hidden;
    border-radius: 2px;
    user-select: none;
}
/* 伪元素遮罩层 */
.img_pic_container figure::before {
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
    z-index: 4;
    background: rgba(58, 12, 5, 0.5);
    content: ' ';
    font-size: 0;
    opacity: 0;
    transition: all .3s;
    cursor: pointer;
}
/* 图片 */
.img_pic_container figure img {
    display: block;
    width: 100%;
    height: 100%;
    transition: all .3s;
}
/* 图片标题 */
.img_pic_container figure figcaption {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 7;
    opacity: 0;
    font-size: 1.5em;
    color: rgb(255, 255, 255);
    transform: translate(-50%, -50%);
    transition: all .3s;
    text-align: center;
    cursor: pointer;
}
/* 悬停图片的时候标题显示 */
.img_pic_container figure:hover figcaption{
    opacity: 1;
}
.img_pic_container figure:hover img{
    transform: scale(1.1, 1.1);
}
/* 悬停图片的时候遮罩显示 */
.img_pic_container figure:hover::before{
    opacity: 1;
}
.img_overlay{
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0, 0, 0, .8);
    display: flex;
    justify-content: center;
    align-items: center;
    opacity: 0;
    transition: all .3s;
    display: none;
    z-index: 99;
}
.img_overlay_prevbtn,
.img_overlay_nextbtn{
    position: absolute;
    width: 50px;
    height: 50px;
    border-radius: 50%;
    border: 2px solid white;
    text-align: center;
    line-height: 50px;
    color: white;
    font-size: 2rem;
    cursor: pointer;
}
.img_overlay_prevbtn{
    left: 20px;
}
.img_overlay_nextbtn{
    right: 20px;
}
.img_overlay_prevbtn:active,
.img_overlay_nextbtn:active{
    background: rgb(241, 241, 241, .4);
}
.img_overlay_nextbtn::after{
    content: "n";
}
.img_overlay_prevbtn::after{
    content: "p";
}
.img_overlay img {
  transform: scale(2, 2);
}

index.js

(function(window,document){

    let canchange=true;
    let curimgindex=0;//默认显示的图片索引

    //公共方法(便于之后对dom的操作)
    const methods={
        //同时添加多个子元素,对象简洁表示法
        appendchilds(parent,...child){
            child.foreach(item=>{
                parent.appendchild(item);
            })
        },
        //选择单个元素
        $(selector,root=document){
            return root.queryselector(selector);
        },
        //选择多个元素
        $$(selector,root=document){
            return root.queryselectorall(selector);
        }
    };

    // 构造函数
    let img=function(options){
        this._init(options);//初始化,对图片进行分类
        this._createelement();//生成dom
        this._bind();//绑定事件
        this._show();//显示到页面上
    }

    //初始化
    img.prototype._init=function({data,inittype,outwrap}){
        this.types=["全部"];//全部分类
        this.all=[];//所有图片
        this.classified={"全部":[]};//分类映射
        this.curtype=inittype;//当前显示的图片分类
        this.outwrap=methods.$(outwrap);//所有dom挂载点
        this.imgcontainer=null;//图片部分容器(不包括分类按钮)
        this.wrap=null;//图片区域总容器(包括分类按钮)
        this.typebtnels=null;//分类按钮数组
        this.figures=null;//图片数组

        this._classify(data);//对图片进行分类
        //console.log(this.classified);//打印分类映射表

    }

    //对图片进行分类
    img.prototype._classify=function(data){
        let srcs=[];//存储已经生成过的图片,避免重复生成

        data.foreach(({type,title,alt,src},index)=>{
            // arr.includes(a) 判断数组中是否存在某个值
            // 如果分类的数组中,没有当前分类,则添加当前分类
            if(!this.types.includes(type)){            
                this.types.push(type);
            }

            //object.keys(obj) 返回obj中所有属性名组成的数组
            //如果属性名中不存在该分类,则添加该分类
            if(!object.keys(this.classified).includes(type)){
                this.classified[type]=[];
            }

            //如果该图片没有生成过
            if(!srcs.includes(src)){
                srcs.push(src);

                //生成图片
                let figure=document.createelement("figure");
                let img=document.createelement("img");
                let figcaption=document.createelement("figcaption");

                img.src=src;
                img.setattribute("alt",alt);
                figcaption.innertext=title;

                methods.appendchilds(figure,img,figcaption);

                //添加到图片数组中
                this.all.push(figure);

                //添加到分类映射中
                this.classified[type].push(this.all.length-1);

            }else{
                //如果该图片已经生成过,就去srcs数组中找到对应图片
                //srcs.findindex(s1=>s1===src) 遍历src数组,找到元素的值为src的,返回其下标
                this.classified[type].push(srcs.findindex(s1=>s1===src));

            }
        })
    }

    //获取对应分类下的图片
    img.prototype._getimgsbytype=function(type){
        //如果分类是全部,就返回all数组
        //否则就去图片映射表里,找到该分类对应的图片的索引;
        //通过map遍历this.all数组,找到这些索引对应的图片
        return type==="全部"?[...this.all]:this.classified[type].map(index=>this.all[index]);
    }

    //生成dom
    img.prototype._createelement=function(){
        let typesbtn=[];
        //根据分类数组,生成所有分类对应的按钮元素
        for(let type of this.types.values()){    
            typesbtn.push(`
                <li class="img_classify_type_btn${ type===this.curtype?' img_classify_type_btn_active':''}">${ type }</li>
            `);
        }

        //console.log(typesbtn);

        //整体模板
        let templates=`
            <ul class="img_classify">${ typesbtn.join("") }</ul>
            <div class="img_pic_container"></div>
        `;

        let wrap=document.createelement("div");
        wrap.classname="img_container";
        wrap.innerhtml=templates;

        this.imgcontainer=methods.$(".img_pic_container",wrap);

        //将分类下对应的图片数组使用扩展运算符展开,添加到图片容器中
        methods.appendchilds(this.imgcontainer,...this._getimgsbytype(this.curtype));

        //取出可能会用到的元素,挂载到this上
        this.wrap=wrap;
        this.typebtnels=[...methods.$$(".img_classify_type_btn",wrap)];
        this.figures=[...methods.$$("figure",wrap)];//使用扩展运算符将取得的dom元素转数组

        //遮罩层
        let overlay=document.createelement("div");
        overlay.classname="img_overlay";
        overlay.innerhtml=`
            <div class="img_overlay_prevbtn"></div>
            <div class="img_overlay_nextbtn"></div>
            <img src="" alt="">
        `;
        methods.appendchilds(this.outwrap,overlay);
        this.overlay=overlay;
        this.previewimg=methods.$("img",overlay);//当前要预览的图片

        this._calcposition(this.figures);//修改每张图片的定位
    }

    //映射关系
    img.prototype._diff=function(curimgs,nextimgs){
        let diffarr=[];//保存映射关系
        // 如:当前[1,2,3,5,6],下一批[3,9,11,12,14]
        // 则映射为[[2,0],..] 
        // 两组图片中国相同的图片为3,3在数组1的下标为2,3在数组2的下标为0,保存这种映射关系
        
        curimgs.foreach((src1,index1)=>{
            //遍历当前src数组,对每一个src,去下一批的src数组中找是否有相同的,有则返回该src在下一批数组中的下标
            let index2=nextimgs.findindex(src2=>src1===src2);

            //
            if(index2!=-1){
                diffarr.push([index1,index2]);
            }
        })

        return diffarr;

    }

    //绑定事件
    img.prototype._bind=function(){
        //解构赋值,获取到e.target
        methods.$(".img_classify",this.wrap).addeventlistener("click",({target})=>{
            if(target.nodename!=="li") return;//如果点的不是li,则返回
            //console.log(target.innertext);
            
            if(!canchange) return;
            canchange=false;

            const type=target.innertext;
            const imgs=this._getimgsbytype(type);//下一轮要显示的所有图片

            //目前出现的图片的所有src
            let curimgs=this.figures.map(figure=>methods.$("img",figure).src);
            //点击后下一批要出现的图片的src
            let nextimgs=imgs.map(figure=>methods.$("img",figure).src);

            const diffarr=this._diff(curimgs,nextimgs);//得到两组图片共有的图片映射关系
            //遍历该映射
            diffarr.foreach(([,index2])=>{//index2是两组中相同的图片在下一组中的索引
                //every() 方法使用指定函数检测数组中的所有元素:
                //如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。
                //如果所有元素都满足条件,则返回 true。
                this.figures.every((figure,index)=>{
                    let src=methods.$("img",figure).src;
                    if(src===nextimgs[index2]){
                        //splice() 方法向/从数组中添加/删除项目
                        this.figures.splice(index,1);//找到相同的图片,从上一轮的图片数组中剔除
                        return false;
                    }
                    return true;
                })
            })

            this._calcposition(imgs);

            let needappendimgs=[];//切换下一轮时需要添加的元素(相同的元素不需要重复添加)
            if(diffarr.length){
                //如果两轮存在相同图片,则相同的图片 不需要重复加载
                let nextindex=diffarr.map(([,index2])=>index2);//相同的图片的下标
                imgs.foreach((figure,index)=>{
                    if(!needappendimgs.includes(index)){
                        needappendimgs.push(figure);//如果该图片不存在,则添加
                    }
                })
            }else{
                //如果不存在相同图片,则所有图片需要加载
                needappendimgs=imgs;
            }

            //隐藏当前所有图片
            this.figures.foreach(figure=>{
                figure.style.transform="scale(0,0) translate(0 100%)";
                figure.style.opacity="0";
            })

            //添加需要新增的图片
            methods.appendchilds(this.imgcontainer,...needappendimgs);

            //显示新一轮的图片            
            settimeout(()=>{
                imgs.foreach(el=>{                    
                    el.style.transform="scale(1,1) translate(0,0)";
                    el.style.opacity="1";    
                    //console.log(el);                
                })
            });

            //销毁上一轮出现的所有图片
            settimeout(()=>{
                this.figures.foreach(figure=>{
                    this.imgcontainer.removechild(figure);            
                })

                this.figures=imgs;

                canchange=true;
            },600);//在css中动画设置的结束时长就是0.6秒

            //切换按钮样式
            this.typebtnels.foreach(btn=>(btn.classname="img_classify_type_btn"));
            target.classname="img_classify_type_btn img_classify_type_btn_active";
        })

        //给每张图片绑定点击事件
        this.imgcontainer.addeventlistener("click",({target})=>{
            if(target.nodename!=="figure" && target.nodename!=="figcaption") return;
            if(target.nodename==="figcaption"){
                target=target.parentnode;
            }

            //显示预览的图片和遮罩层
            const src=methods.$("img",target).src;
            curimgindex=this.figures.findindex(figure=>src===methods.$("img",figure).src);
            this.previewimg.src=src;
            this.overlay.style.display="flex";
            
            settimeout(()=>{
                this.overlay.style.opacity="1";
            })
        })

        //点击遮罩层,淡出
        this.overlay.addeventlistener("click",()=>{
            this.overlay.style.opacity="0";
            settimeout(()=>{
                this.overlay.style.display="none";
            },300);//300毫秒是css中transition设置的时间
        })

        //上一张下一张按钮绑定
        methods.$(".img_overlay_prevbtn",this.overlay).addeventlistener("click",e=>{
            //阻止事件冒泡,导致触发点击遮罩隐藏遮罩的情况
            e.stoppropagation();
            curimgindex=curimgindex===0?this.figures.length-1:curimgindex-1;
            this.previewimg.src=methods.$("img",this.figures[curimgindex]).src;
        })
        methods.$(".img_overlay_nextbtn",this.overlay).addeventlistener("click",e=>{
            e.stoppropagation();
            curimgindex=curimgindex===this.figures.length-1?0:curimgindex+1;
            this.previewimg.src=methods.$("img",this.figures[curimgindex]).src;
        })
    }

    //显示到页面上
    img.prototype._show=function(){
        methods.appendchilds(this.outwrap,this.wrap);//把图片总容器挂载到指定的外容器上

        //演示器实现进场动画
        settimeout(()=>{
            //让所有图片显示
            this.figures.foreach(figure=>{
                figure.style.transform="scale(1,1) translate(0,0)";
                figure.style.opacity="1";
            })
        },0)

    }

    //计算每张图片的top和left
    img.prototype._calcposition=function(figures){
        figures.foreach((figure,index)=>{
            //140是每张图片的高度,15是每张图片垂直方向的间隙
            //240是每张图片的宽度,15是每张图片水平方向的间隙
            figure.style.top=parseint(index/4)*140+parseint(index/4)*15+"px";
            figure.style.left=parseint(index%4)*(240+15)+"px";
            figure.style.transform="scale(0,0) translate(0,-100%)";//让特效更丰富
        })

        //设置图片容器高度
        var num=figures.length;//图片数量
        if(num<=4){
            this.imgcontainer.style.height="140px";
        }else{
            this.imgcontainer.style.height=math.ceil(num/4)*140+(math.ceil(num/4)-1)*15+"px";
        }
        
    }

    window.$img=img;//暴露到全局

})(window,document);

data.js(数据)

const data = [

  {
    type: 'javascript',
    title: 'es6快速入门',
    alt: 'es6快速入门',
    src: './images/1.jpg'
  },

  {
    type: 'javascript',
    title: 'javascript实现二叉树算法',
    alt: 'javascript实现二叉树算法',
    src: './images/2.jpg'
  },

  {
    type: 'javascript',
    title: 'canvas绘制时钟',
    alt: 'canvas绘制时钟',
    src: './images/3.jpg'
  },

  {
    type: 'javascript',
    title: '基于websocket的火拼俄罗斯',
    alt: '基于websocket的火拼俄罗斯',
    src: './images/15.jpg'
  },

  {
    type: '前端框架',
    title: 'react知识点综合运用实例',
    alt: 'react知识点综合运用实例',
    src: './images/4.jpg'
  },

  {
    type: '前端框架',
    title: 'react组件',
    alt: 'react组件',
    src: './images/5.jpg'
  },

  {
    type: '前端框架',
    title: 'vue+webpack打造todo应用',
    alt: 'vue+webpack打造todo应用',
    src: './images/6.jpg'
  },

  {
    type: '前端框架',
    title: 'vue.js入门基础',
    alt: 'vue.js入门基础',
    src: './images/7.jpg'
  },

  {
    type: '前端框架',
    title: '使用vue2.0实现购物车和地址选配功能',
    alt: '使用vue2.0实现购物车和地址选配功能',
    src: './images/8.jpg'
  },

  {
    type: 'react',
    title: 'react知识点综合运用实例',
    alt: 'react知识点综合运用实例',
    src: './images/4.jpg'
  },

  {
    type: 'react',
    title: 'react组件',
    alt: 'react组件',
    src: './images/5.jpg'
  },

  {
    type: 'vue.js',
    title: 'vue+webpack打造todo应用',
    alt: 'vue+webpack打造todo应用',
    src: './images/6.jpg'
  },

  {
    type: 'vue.js',
    title: 'vue.js入门基础',
    alt: 'vue.js入门基础',
    src: './images/7.jpg'
  },

  {
    type: 'vue.js',
    title: '使用vue2.0实现购物车和地址选配功能',
    alt: '使用vue2.0实现购物车和地址选配功能',
    src: './images/8.jpg'
  }

]