原生js实现放大镜组件


Posted in Javascript onJanuary 22, 2021

本文实例为大家分享了js实现放大镜组件开发的具体代码,供大家参考,具体内容如下

功能需求:

1、根据图片数组创建图标列表;
2、鼠标滑过图标时,当前图标增加红色边框;
3、鼠标滑过图标时,上方图片区域显示对应的图片,右侧显示放大后的图片内容;
4、鼠标在图片区域移动时,在右侧实现放大效果;
5、下方图标列表,点击左右按钮,实现翻页效果;
6、当图标内容不够一页时,只移动到最后一个图标的位置;

以京东的详情页为例,看一下效果:

原生js实现放大镜组件

放大镜内容写在 Zoom.js 文件里,下方的图标列表内容写在 IconList.js 文件里,当鼠标滑过下面的图标时,需要更改放大镜里div的背景图片,这里用到了事件抛发。

下面附上代码:

html结构 :

<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="UTF-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>zoom</title>
</head>
<body>
 <script type="module">
  import Zoom from './js/Zoom.js';
  //图标数组
  let list=["a_icon.jpg","e_icon.jpg","f_icon.jpg","g_icon.jpg","h_icon.jpg","i_icon.jpg","j_icon.jpg",];
  init();
  function init(){
   let zoom=new Zoom(list,"./img/");
   zoom.appendTo("body");
  }
 </script>
</body>
</html>

Zoom.js文件,创建放大镜组件:

import Utils from "./Utils.js";
import IconList from './IconList.js';
export default class Zoom{
 static styles=false;
 static small_width=450;
 static mask_width=303.75;
 static zoom_width=540;
 static SET_BG_IMG="set_bg_img";
 constructor(_list,_basePath){
  if(_basePath) _list=_list.map(item=>_basePath+item);
  //创建外层的div容器
  this.elem=this.createE();
  //监听事件,改变zoomSmall的背景图
  document.addEventListener(Zoom.SET_BG_IMG,e=>this.setBgImg(e));
  //创建下方的icon列表
  this.createIconList(_list,this.elem);
 }
 createE(){
  //创建外层div容器
  let div=Utils.createE("div");
  div.className="zoomContainer";
  div.innerHTML=`<div class="zoomSmall" id="zoomSmall"><div class="zoomMask" id="zoomMask"></div></div>
  <div class="zoomContent" id="zoomCont"></div>`;
  //设置样式
  Zoom.setStyle();
  //获取样式
  Utils.getIdElem(div,this);
  //监听鼠标滑入事件
  this.zoomSmall.addEventListener("mouseenter",e=>this.mouseHandler(e));
  return div;
 }
 appendTo(parent){
  Utils.appendTo(this.elem,parent);
 }
 setBgImg(e){
  //设置背景图片
  this.zoomSmall.style.backgroundImage=`url(${e.src})`;
  this.zoomCont.style.backgroundImage=`url(${e.src})`;
 }
 createIconList(list,parent){
  //创建下方icon图标列表
  let iconList=new IconList(list);
  Utils.appendTo(iconList.elem,parent);
 }
 mouseHandler(e){
  switch (e.type) {
   case "mouseenter":
    //鼠标滑入后,显示遮罩和右侧大图片
    this.zoomMask.style.display="block";
    this.zoomCont.style.display="block";
    //监听鼠标移动和滑出事件
    this.mouseHandlers=e=>this.mouseHandler(e);
    this.zoomSmall.addEventListener("mousemove",this.mouseHandlers);
    this.zoomSmall.addEventListener("mouseleave",this.mouseHandlers);
    break;
   case "mousemove":
    //遮罩移动
    this.zoomMaskMove(e);
    break;
   case "mouseleave":
    //鼠标滑出后,显示遮罩和右侧大图片
    this.zoomMask.style.display="none";
    this.zoomCont.style.display="none";
    //移除鼠标移动和滑出事件
    this.zoomSmall.removeEventListener("mousemove",this.mouseHandlers);
    this.zoomSmall.removeEventListener("mouseleave",this.mouseHandlers);
    break;
  }
 }
 zoomMaskMove(e){
  //遮罩移动
  let rect=this.elem.getBoundingClientRect();
  //计算let和top的值,等于鼠标的坐标-父容器的left值-遮罩的一半宽
  let x=e.clientX-rect.x-Zoom.mask_width/2;
  let y=e.clientY-rect.y-Zoom.mask_width/2;
  //判断left和top的范围
  if(x<0) x=0;
  if(x>Zoom.small_width-Zoom.mask_width) x=Zoom.small_width-Zoom.mask_width;
  if(y<0) y=0;
  if(y>Zoom.small_width-Zoom.mask_width) y=Zoom.small_width-Zoom.mask_width;
  this.zoomMask.style.left=x+"px";
  this.zoomMask.style.top=y+"px";
  //大图片移动
  this.zoomContMove(x,y);
 }
 zoomContMove(_x,_y){
  //计算大图片的背景定位,公式:zoom的宽/mask的宽=zoom的背景left值/mask的left值
  let x=-Zoom.zoom_width/Zoom.mask_width*_x;
  let y=-Zoom.zoom_width/Zoom.mask_width*_y;
  this.zoomCont.style.backgroundPosition=x+"px "+y+"px";
 }
 static setStyle(){
  //设置样式
  if(Zoom.styles) return;
  Zoom.styles=true;
  Utils.insertCss(".zoomContainer",{
   width:Zoom.small_width+"px",
   height:Zoom.small_width+"px",
   position:"relative"
  })
  Utils.insertCss(".zoomSmall",{
   width:Zoom.small_width+"px",
   height:Zoom.small_width+"px",
   border: "1px solid #000",
   backgroundSize: "100% 100%",
   position:"absolute",
   left:"0px",
   top:"0px"
  })
  Utils.insertCss(".zoomMask",{
   width: this.mask_width + "px",
   height: this.mask_width + "px",
   backgroundColor: "rgba(200,170,0,0.3)",
   position: "absolute",
   left: "0px",
   top: "0px",
   display: "none"
  })
  Utils.insertCss(".zoomContent",{
   width: this.zoom_width + "px",
   height: this.zoom_width + "px",
   border: "1px solid #ccc",
   position: "absolute",
   left: (this.small_width + 2) + "px",
   top: "0px",
   display: "none"
  })
 }
}

IconList.js文件,创建下方图标列表,并完成翻页效果:

import Utils from "./Utils.js";
import Zoom from "./Zoom.js";
export default class IconList{
 static styles=false;
 static num=5;//每页显示的图标数
 static gap=0;//表示li的左右间距
 position=0;//当前显示的图标为第几页
 x=0;//列表的left值
 prepIcon;//上一个点击的图标
 static SET_BG_IMG="set_bg_img";
 constructor(list){
  this.list=list;
  this.elem=this.createE();
 }
 createE(){
  //创建外层容器
  let div=Utils.createE("div");
  div.className="iconContainer";
  div.innerHTML=`<img class="prevBtn" src="./img/prev.png"><div class="iconListCont">${this.createIcon()}</div><img class="nextBtn" src="./img/next.png">`;
  //设置css样式
  IconList.setStyles(this.list);
  //获取元素
  Utils.getIdElem(div,this);
  //外层容器监听点击事件
  div.addEventListener("click",e=>this.clickHandler(e));
  //图标列表监听鼠标滑过事件
  this.iconList.addEventListener("mouseover",e=>this.mouseHandler(e));
  //默认显示第一个图标的边框
  this.setIconState(this.iconList.firstElementChild);
  //默认显示第一个图片
  this.setBgImg(this.iconList.firstElementChild.firstElementChild);
  return div;
 }
 createIcon(){
  //创建图标列表
  let str=`<ul class="iconList clearfix" id="iconList">`;
  this.list.forEach(item=>{
   str+=`<li><img src="${item}"></li>`;
  })
  str+="</ul>";
  return str;
 }
 clickHandler(e){
  let src=e.target.src;
  //如果点击的不是左右按钮,直接跳出
  if(!/prev/.test(src)&&!/next/.test(src)) return;
  //每一个li的实际宽度,width+border+margin
  let liWidth=54+4+IconList.gap;
  //page为一共有几个整数页
  let page=Math.floor(this.list.length/IconList.num)-1;
  //remainder为最后不够一页的剩余图标数
  let remainder=this.list.length%IconList.num;
  if(/prev/.test(src)){
   //如果点击的是上一页按钮
   if(this.x===0) return;
   //移动到最后一页时
   if(this.position===0&&remainder>0){
    //移动的距离加等于li宽度*剩余图标数
    this.x+=liWidth*remainder;
   }
   else if(this.position<=page){
    this.position--;
    //移动的距离加等于li的宽度*每页显示的图标数(5个)
    this.x+=liWidth*IconList.num;
   }
  }else if(/next/.test(src)){
   //如果点击的是下一页按钮
   if(this.x===-(this.list.length-IconList.num)*liWidth) return;
   if(this.position===page&&remainder>0){
    //移动的距离减等于li宽度*剩余图标数
    this.x-=liWidth*remainder;
   }
   else if(this.position<page){
    this.position++;
    //移动的距离减等于li的宽度*每页显示的图标数(5个)
    this.x-=liWidth*IconList.num;
   }
  }
  //设置图标列表的left值
  this.iconList.style.left=this.x+"px";
 }
 mouseHandler(e){
  //如果滑过的不是Img标签,直接跳出
  if(e.target.constructor!==HTMLImageElement) return;
  //设置背景图片
  this.setBgImg(e.target);
  //设置当前滑过图标的样式
  this.setIconState(e.target.parentElement);
 }
 setIconState(target){
  //移除上一个滑过图标的active样式
  if(this.prepIcon) Utils.removeClass(this.prepIcon,"active");
  //将当前滑过的对象赋值给this.prepIcon
  this.prepIcon=target;
  //给当前滑过图标增加active样式
  Utils.addClass(this.prepIcon,"active");
 }
 setBgImg(target){
  //抛发事件,将当前图片的src传过去
  let src=target.src.replace("_icon","");
  let evt=new Event(IconList.SET_BG_IMG);
  evt.src=src;
  document.dispatchEvent(evt);
 }
 static setStyles(list){
  //设置样式
  if(IconList.styles) return;
  IconList.styles=true;
  Utils.insertCss(".iconContainer",{
   width:Zoom.small_width+2+"px",
   height: "58px",
   position: "absolute",
   top: Zoom.small_width+2+"px",
   left: "0px",
  })
  Utils.insertCss(".iconContainer>img",{
   width:"22px",
   height:"32px",
   cursor:"pointer",
   position:"absolute",
   top:"13px",
  })
  Utils.insertCss(".prevBtn",{
   left:"8px"
  })
  Utils.insertCss(".nextBtn",{
   right:"8px"
  })
  Utils.insertCss(".iconListCont",{
   width:Zoom.small_width-30*2+"px",
   height:"58px",
   position:"relative",
   left:"30px",
   overflow:"hidden"
  })
  IconList.gap=((Zoom.small_width-30*2)-(54+4)*IconList.num)/IconList.num;
  Utils.insertCss(".iconList",{
   width:(54+4+IconList.gap)*list.length+"px",
   listStyle:"none",
   padding:"0px",
   margin:"0px",
   position:"absolute",
   left:"0px",
   top:"0px",
   transition:"all .3s"
  })
  Utils.insertCss(".iconList li",{
   float:"left",
   width:"54px",
   height:"54px",
   margin:"0px "+IconList.gap/2+"px",
   cursor:"pointer",
   border:"2px solid transparent"
  })
  Utils.insertCss(".iconList li.active",{
   borderColor:"#f00"
  })
  Utils.insertCss(".iconList li>img",{
   width:"54px",
   height:"54px"
  })
  Utils.insertCss(".clearfix::after",{
   content:"\".\"",
   display:"block",
   height:"0px",
   clear:"both",
   overflow:"hidden",
   visibility:"hidden"
  })
 }
}

Utils.js文件,是一个工具包:

export default class Utils{
 static createE(elem,style,prep){
  elem=document.createElement(elem);
  if(style) for(let prop in style) elem.style[prop]=style[prop];
  if(prep) for(let prop in prep) elem[prop]=prep[prop];
  return elem;
 }
 static appendTo(elem,parent){
  if (parent.constructor === String) parent = document.querySelector(parent);
  parent.appendChild(elem);
 }
 static insertBefore(elem,parent){
  if(parent.constructor === String) parent=document.querySelector(parent);
  parent.insertBefore(elem,parent.firstElementChild);
 }
 static randomNum(min,max){
  return Math.floor(Math.random*(max-min)+min);
 }
 static randomColor(alpha){
  alpha=alpha||Math.random().toFixed(1);
  if(isNaN(alpha)) alpha=1;
  if(alpha>1) alpha=1;
  if(alpha<0) alpha=0;
  let col="rgba(";
  for(let i=0;i<3;i++){
   col+=Utils.randomNum(0,256)+",";
  }
  col+=alpha+")";
  return col;
 }
 static insertCss(select,styles){
  if(document.styleSheets.length===0){
   let styleS=Utils.createE("style");
   Utils.appendTo(styleS,document.head);
  }
  let styleSheet=document.styleSheets[document.styleSheets.length-1];
  let str=select+"{";
  for(var prop in styles){
   str+=prop.replace(/[A-Z]/g,function(item){
    return "-"+item.toLocaleLowerCase();
   })+":"+styles[prop]+";";
  }
  str+="}"
  styleSheet.insertRule(str,styleSheet.cssRules.length);
 }
 static getIdElem(elem,obj){
  if(elem.id) obj[elem.id]=elem;
  if(elem.children.length===0) return obj;
  for(let i=0;i<elem.children.length;i++){
   Utils.getIdElem(elem.children[i],obj);
  }
 }
 static addClass(elem,className){
  let arr=(elem.className+" "+className).match(/\S+/g);
  arr=arr.filter((item,index)=>arr.indexOf(item,index+1)<0)
  elem.className=arr.join(" ");
 }
 static removeClass(elem,className){
  if(!elem.className) return;
  let arr=elem.className.match(/\S+/g);
  let arr1=className.match(/\S+/g);
  arr1.forEach(item=>{
   arr=arr.filter(t=>t!==item)
  })
  elem.className=arr.join(" ");
 }
 static hasClass(elem,className){
  if(!elem.className) return false;
  let arr=elem.className.match(/\S+/g);
  let arr1=className.match(/\S+/g);
  let res;
  arr1.forEach(item=>{
   res= arr.some(it=>it===item)
  })
  return res;
 }
 static loadImg({list,basePath,callback}){
  if(!list || list.length===0) return;
  if(basePath) list=list.map(item=>basePath+item);
  let img=Utils.createE("img");
  img.data={
   list:list,
   callback:callback,
   resultList:[],
   num:0
  }
  img.addEventListener("load",Utils.loadImgHandler);
  img.src=list[img.data.num];
 }
 static loadImgHandler(e){
  let data=e.currentTarget.data;
  data.resultList.push(e.currentTarget.cloneNode(false));
  data.num++;
  if(data.num>data.list.length-1){
   e.currentTarget.removeEventListener("load",Utils.loadImgHandler);
   data.callback(data.resultList);
   data=null;
   return;
  }
  e.currentTarget.src=data.list[data.num];
 }
}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
dojo随手记 gird组件引用
Feb 24 Javascript
JavaScript异步编程:异步数据收集的具体方法
Aug 19 Javascript
JS实现多物体缓冲运动实例代码
Nov 29 Javascript
js获取上传文件的绝对路径实现方法
Aug 02 Javascript
jQuery事件绑定方法学习总结(推荐)
Nov 21 Javascript
浅谈vue,angular,react数据双向绑定原理分析
Nov 28 Javascript
p5.js入门教程之键盘交互
Mar 19 Javascript
vue中vee validate表单校验的几种基本使用
Jun 25 Javascript
详解Angular如何正确的操作DOM
Jul 06 Javascript
vue-cli3脚手架的配置及使用教程
Aug 28 Javascript
vue多层嵌套路由实例分析
Mar 19 Javascript
JavaScript中使用Spread运算符的八种方法总结
Jun 18 Javascript
Vue仿Bibibili首页的问题
Jan 21 #Vue.js
如何在vue 中使用柱状图 并自修改配置
Jan 21 #Vue.js
Vue看了就会的8个小技巧
Jan 21 #Vue.js
原生js实现滑块区间组件
Jan 20 #Javascript
原生js实现下拉框选择组件
Jan 20 #Javascript
原生js实现自定义滚动条组件
Jan 20 #Javascript
原生js实现自定义滚动条
Jan 20 #Javascript
You might like
消息持续发送的完整例子
2006/10/09 PHP
PHP开发框架kohana3 自定义路由设置示例
2014/07/14 PHP
PHP信号量基本用法实例详解
2016/02/12 PHP
JS中判断null、undefined与NaN的方法
2014/03/24 Javascript
js怎么覆盖原有方法实现重写
2014/09/04 Javascript
js实现TAB切换对应不同颜色的代码
2015/08/31 Javascript
springMVC结合AjaxForm上传文件
2016/07/12 Javascript
ReactNative之键盘Keyboard的弹出与消失示例
2017/07/11 Javascript
Vue-resource拦截器判断token失效跳转的实例
2017/10/27 Javascript
详解vue 单页应用(spa)前端路由实现原理
2018/04/04 Javascript
vue.js实现简单的计算器功能
2020/02/22 Javascript
Electron整合React使用搭建开发环境的步骤详解
2020/06/07 Javascript
使用Python解析JSON数据的基本方法
2015/10/15 Python
新手如何快速入门Python(菜鸟必看篇)
2017/06/10 Python
Django教程笔记之中间件middleware详解
2018/08/01 Python
python在TXT文件中按照某一字符串取出该字符串所在的行方法
2018/12/10 Python
django解决跨域请求的问题详解
2019/01/20 Python
Python调用百度根据经纬度查询地址的示例代码
2019/07/07 Python
python使用协程实现并发操作的方法详解
2019/12/27 Python
Python新手如何进行闭包时绑定变量操作
2020/05/29 Python
Python 连接 MySQL 的几种方法
2020/09/09 Python
利用pipenv和pyenv管理多个相互独立的Python虚拟开发环境
2020/11/01 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
详解通过变换矩阵实现canvas的缩放功能
2019/01/14 HTML / CSS
HTML5的结构和语义(5):内嵌媒体
2008/10/17 HTML / CSS
西安众合通用.net笔试题
2013/03/18 面试题
技术总监个人的自我评价范文
2013/12/18 职场文书
幼儿园大班家长评语
2014/04/17 职场文书
新闻发布会策划方案
2014/06/12 职场文书
小学一年级学生评语大全
2014/12/25 职场文书
委托书的样本
2015/01/28 职场文书
2015年宣传工作总结
2015/04/08 职场文书
经费申请报告范文
2015/05/18 职场文书
公司宣传语大全
2015/07/13 职场文书
html5中sharedWorker实现多页面通信的示例代码
2021/05/07 Javascript
Python数组变形的几种实现方法
2022/05/30 Python