按照国际惯例先放效果图
贴代码:
index.html
<!DOCTYPE html> <html lang="en"head> Meta charset="UTF-8"title>index</link rel="stylesheet" href="index.css"bodydiv 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="data.js"> const img=new $Img({ data,initType:"JavaScript,//默认显示的分类 outWrap:#wrap所有DOM挂载点 }); 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: #wrap{ width: 1065px; 0 auto; 30px; rgb(255,255); border-radius: 2px; margin-top: 100px; } .img_container{ font-size: 10px; } .img_classify_type_btn{ inline-block; .2em 1em; 1.6em; margin-right: 10px; cursor: pointer; border: 1px solid #e95a44; outline: none; color: #e95a44; transition: all .4s; user-select: none;/*文字不允许用户选中*/ 2px; } .img_classify_type_btn_active{ #fff; } .img_pic_container{ position: relative; width: 1005px; flex; flex-wrap: wrap; all .6s cubic-bezier(0.77,0.175,1);动画效果*/ } .img_pic_container figure{ 240px; height: 140px; absolute; transform: scale(0,0); opacity: 0; overflow: hidden; none; } 伪元素遮罩层 */ .img_pic_container figure::before { block; 100%; top: left: z-index: 4; rgba(58,12,5,0.5); content: ' '; all .3s; pointer; } 图片 .img_pic_container figure img { all .3s; } 图片标题 .img_pic_container figure figcaption { 50%; 7; 1.5em; translate(-50%,-50%); text-align: center; 悬停图片的时候标题显示 .img_pic_container figure:hover figcaption{ 1; } .img_pic_container figure:hover img{ scale(1.1,1.1); } 悬停图片的时候遮罩显示 .img_pic_container figure:hover::before{ .img_overlay{ fixed; right: bottom: background-color: rgba(0,.8); justify-content: align-items: 99; } .img_overlay_prevbtn,.img_overlay_nextbtn{ 50px; 2px solid white; line-height: white; 2rem; pointer; } .img_overlay_prevbtn{ 20px; } .img_overlay_nextbtn{ .img_overlay_prevbtn:active,.img_overlay_nextbtn:active{ rgb(241,241,.4); } .img_overlay_nextbtn::after{ "N"; } .img_overlay_prevbtn::after{ "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); },1)">选择多个元素 $$(selector,1)"> root.querySelectorAll(selector); } }; 构造函数 let Img=(options){ this._init(options);初始化,对图片进行分类 this._createElement();生成DOM this._bind();绑定事件 this._show();显示到页面上 } 初始化 Img.prototype._init=({data,initType,outWrap}){ this.types=["全部"];全部分类 this.all=[];所有图片 this.classified={"全部":[]};分类映射 this.curType=initType;当前显示的图片分类 this.outWrap=methods.$(outWrap);所有DOM挂载点 this.imgContainer=null;图片部分容器(不包括分类按钮) this.wrap=图片区域总容器(包括分类按钮) this.typeBtnEls=分类按钮数组 this.figures=图片数组 this._classify(data);对图片进行分类 console.log(this.classified);//打印分类映射表 } 对图片进行分类 Img.prototype._classify=(data){ let srcs=[];存储已经生成过的图片,避免重复生成 data.forEach(({type,title,alt,src},index)=>{ arr.includes(a) 判断数组中是否存在某个值 如果分类的数组中,没有当前分类,则添加当前分类 if(!this.types.includes(type)){ .types.push(type); } Object.keys(obj) 返回obj中所有属性名组成的数组 如果属性名中不存在该分类,则添加该分类 if(!Object.keys(.classified).includes(type)){ this.classified[type]=[]; } 如果该图片没有生成过 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"title; methods.appendChilds(figure,img,figcaption); 添加到图片数组中 .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=(type){ 如果分类是全部,就返回all数组 否则就去图片映射表里,找到该分类对应的图片的索引; 通过map遍历this.all数组,找到这些索引对应的图片 return type==="全部"?[...this.all]:this.classified[type].map(index=>.all[index]); } 生成DOM Img.prototype._createElement=(){ let typesBtn=[]; 根据分类数组,生成所有分类对应的按钮元素 for(let type of .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"将分类下对应的图片数组使用扩展运算符展开,添加到图片容器中 methods.appendChilds(this.imgContainer,...this._getImgsByType(.curType)); 取出可能会用到的元素,挂载到this上 wrap; this.typeBtnEls=[...methods.$$(".img_classify_type_btn"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(.outWrap,overlay); this.overlay=overlay; this.previewImg=methods.$("img",overlay);当前要预览的图片 this._calcPosition(this.figures);修改每张图片的定位 映射关系 Img.prototype._diff=(curImgs,nextImgs){ let diffArr=[];保存映射关系 如:当前[1,2,3,6],下一批[3,9,11,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]); } }) diffArr; } 绑定事件 Img.prototype._bind=(){ 解构赋值,获取到e.target methods.$(".img_classify",this.wrap).addEventListener("click",({target})=>if(target.nodeName!=="LI") return;如果点的不是li,则返回 console.log(target.innerText); if(!canChange) ; canChange=false; const type=target.innerText; const imgs=this._getImgsByType(type);下一轮要显示的所有图片 目前出现的图片的所有src let curImgs=this.figures.map(figure=>methods.$("img"点击后下一批要出现的图片的src let nextImgs=imgs.map(figure=>methods.$("img"this._diff(curImgs,nextImgs);得到两组图片共有的图片映射关系 遍历该映射 diffArr.forEach(([,index2])=>{index2是两组中相同的图片在下一组中的索引 every() 方法使用指定函数检测数组中的所有元素: 如果数组中检测到有一个元素不满足,则整个表达式返回 false ,且剩余的元素不会再进行检测。 如果所有元素都满足条件,则返回 true。 this.figures.every((figure,index)=>{ let src=methods.$("img"if(src===nextImgs[index2]){ splice() 方法向/从数组中添加/删除项目 this.figures.splice(index,1);找到相同的图片,从上一轮的图片数组中剔除 return ; } ; }) }) ._calcPosition(imgs); let needAppendImgs=[];切换下一轮时需要添加的元素(相同的元素不需要重复添加) if(diffArr.length){ 如果两轮存在相同图片,则相同的图片 不需要重复加载 let nextIndex=diffArr.map(([,index2])=>index2);相同的图片的下标 imgs.forEach((figure,1)">{ needAppendImgs.includes(index)){ needAppendImgs.push(figure);如果该图片不存在,则添加 } }) }如果不存在相同图片,则所有图片需要加载 needAppendImgs=imgs; } 隐藏当前所有图片 this.figures.forEach(figure=>{ figure.style.transform="scale(0,0) translate(0 100%)"; figure.style.opacity="0"; }) 添加需要新增的图片 methods.appendChilds(.imgContainer,...needAppendImgs); 显示新一轮的图片 setTimeout(()=>{ imgs.forEach(el=>{ el.style.transform="scale(1,1) translate(0,0)"; el.style.opacity="1"; console.log(el); }) }); 销毁上一轮出现的所有图片 setTimeout(()=>.imgContainer.removeChild(figure); }) imgs; canChange=; },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",1)">if(target.nodeName!=="FIGURE" && target.nodeName!=="FIGCAPTION") ; if(target.nodeName==="FIGCAPTION"){ target=target.parentNode; } 显示预览的图片和遮罩层 const src=methods.$("img"this.figures.findIndex(figure=>src===methods.$("img"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毫秒是css中transition设置的时间 }) 上一张下一张按钮绑定 methods.$(".img_overlay_prevbtn",1)">this.overlay).addEventListener("click",e=>阻止事件冒泡,导致触发点击遮罩隐藏遮罩的情况 e.stopPropagation(); curImgIndex=curImgIndex===0?this.figures.length-1:curImgIndex-1this.previewImg.src=methods.$("img",1)">.figures[curImgIndex]).src; }) methods.$(".img_overlay_nextbtn",1)">{ e.stopPropagation(); curImgIndex=curImgIndex===this.figures.length-1?0:curImgIndex+1.figures[curImgIndex]).src; }) } 显示到页面上 Img.prototype._show=(){ methods.appendChilds(this.outWrap,1)">this.wrap);把图片总容器挂载到指定的外容器上 演示器实现进场动画 setTimeout(()=>让所有图片显示 { figure.style.transform="scale(1,1)">; figure.style.opacity="1"; }) },0) } 计算每张图片的top和left Img.prototype._calcPosition=(figures){ figures.forEach((figure,1)">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"; }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'快速入门'快速入门' },{ type: 'JavaScript''Javascript实现二叉树算法''Canvas绘制时钟''./images/3.jpg''基于websocket的火拼俄罗斯''前端框架''React知识点综合运用实例''React组件''./images/5.jpg''Vue+Webpack打造todo应用''Vue.js入门基础''./images/7.jpg'功能'功能''React' } ]