ES6实现图片切换特效(图片墙效果)
程序员文章站
2022-07-02 11:03:52
按照国际惯例先放效果图 贴代码: index.html index
按照国际惯例先放效果图
贴代码:
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' } ]