【JS案例】JS实现图片放大镜功能

JS案例·图片放大镜
🌟效果展示
🌟HTML结构
🌟CSS样式
🌟实现思路
🌟具体实现
1.初始化数据图片
2.获取所需DOM元素
3.初始化页面
初始化缩略图
绑定事件
🌟完整代码
🌟写在最后
🌟效果展示

🌟HTML结构
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="./index.css">
</head>
<body><!--最外层容器 --><div class="container"><!-- 左侧原图 --><div class="left-img"><!-- 遮罩层 --><div class="mask"></div></div><!-- 右侧放大图片 --><div class="right-img"></div><!-- 缩略图集合 --><div class="img-list-wrapper"><ul class="img-list"><li></li></ul></div></div><script src="./index.js"></script>
</body>
</html>
🌟CSS样式
* {margin: 0;padding: 0;list-style: none;
}.container {width: 1000px;height: 600px;margin: 50px auto;font-size: 0;
}.left-img {width: 490px;height: 510px;margin-right: 16px;border: 1px solid #eee;display: inline-block;/* 图片 */background-image: url(./images/imgA_2.jpg);background-repeat: no-repeat;background-position: center;background-size: cover;/* 遮罩层相对我进行定位 */position: relative;
}
.mask {width: 230px;height: 230px;background-image: url(./images/bg.png);position: absolute;top: 0;left: 0;/* opacity: 0; */
}
.right-img {width: 490px;height: 510px;border: 1px solid #eee;display: inline-block;background-image: url(./images/imgA_3.jpg);background-repeat: no-repeat;/* opacity: 0; */
}.img-list-wrapper {width: 490px;text-align: center;margin-top: 10px;
}
.img-list {display: inline-block;
}
.img-list li {display: inline-block;width: 60px;height: 60px;margin: 0 5px;cursor: pointer;background-image: url(./images/imgA_1.jpg);background-repeat: no-repeat;border: 2px solid #000;/* border: 1px solid #eee; */
}
🌟实现思路
在敲完上面HTML文件和CSS文件后可以看到下图效果(图片素材及完整代码文末可下载):

可以看到整体分为四块:
1. 阴影区域:其实是一张很小的像素图铺满元素形成,后续跟随鼠标移动,所选区域在右侧高清展示。
2.图片大图:就是展示的图片。
3.缩略图列表:点击切换图片。
4.高清大图展示区域。
在项目中②和③一般可以用同一种图,④会使用一张高清图。我们这里是使用三张图片来开发,缩略图,普通展示图,高清图。
因为①和④是鼠标移入才出现,所以这里 给.mask和.right-img加上opacity: 0;属性如下:

接下来我们需要做的就是在鼠标移入和移出时在右侧展示与隐藏高清图,从而实现图片放大镜效果。
🌟具体实现
1.初始化数据图片
这里用的就是本地图片,所以先初始化数据图片,三种大小对应缩略图,展示图,高清图:
// 初始化数据图片
var imgs = {// 小图small: ['imgA_1.jpg', 'imgB_1.jpg', 'imgC_1.jpg'],// 中图middle: ['imgA_2.jpg', 'imgB_2.jpg', 'imgC_2.jpg'],// 大图large: ['imgA_3.jpg', 'imgB_3.jpg', 'imgC_3.jpg']
}
2.获取所需DOM元素
因为需要频繁操作DOM元素,这里简单封装一个获取DOM元素方法:
// 单一元素
function $(selector) {return document.querySelector(selector);
}// 多个元素
function $$(selector) {return document.querySelectorAll(selector);
}
接下来获取需要用到的元素:

3.初始化页面
初始化缩略图
初始化所有缩略图,及③区域的缩略图列表。拿到缩略图值拼接字符串,将<li></li>插入页面,并默认选中第一张缩略图:
// 初始化所有缩略图let str = '';for(var i=0; i<imgs.small.length; i++) {str += '<li style="background-image: url(./images/'+ imgs.small[i] +');"></li>'}smallImg.innerHTML = str
// 默认选中第一个缩略图
$('.img-list li').style.border = '2px solid #000';

绑定事件
该效果中一共有两种事件,一个是点击缩略图,另一个是鼠标移入移出。
点击缩略图切换展示图片
smallImg.onclick = function (e) {// 判断我点击的元素是li元素if (e.target.tagName == 'LI') {// 让所有li元素取消borderlet lis = $$('li');for (let i = 0; i < lis.length; i++) {lis[i].style.border = 'none';}// 让选中的li元素添加bordere.target.style.border = '2px solid #000';// 点击缩略图后,原图和大图也需要跟着变换,// 点的是第几个元素, 获取元素索引// [1,2,3].indexOf(3) == 2let index = [].indexOf.call(lis, e.target);midImg.style.backgroundImage = 'url(./images/' + imgs.middle[index] + ')';largeImg.style.backgroundImage = 'url(./images/' + imgs.large[index] + ')';} }
移入移出事件
这里需要计算遮罩层离边框的距离,可得到的距离有:
绿色:边框距离浏览器左边的距离
红色:鼠标距离浏览器左边的距离
黄色:遮罩层一半的距离
通过这几个值就可求出遮罩层距离边框的距离left了,同理top值一样的求法
鼠标移入
midImg.onmousemove = function (e) {// 让遮罩层和大图展示mask.style.opacity = 1;largeImg.style.opacity = 1;// 根据鼠标位置计算遮罩层的位置let left = e.clientX - midImg.offsetLeft - mask.offsetWidth / 2;// 同理let top = e.clientY - midImg.offsetTop - mask.offsetHeight / 2;// 边界条件if (left <= 0) {left = 0;}if (top <= 0) {top = 0;}if (left >= midImg.offsetWidth - mask.offsetWidth) {left = midImg.offsetWidth - mask.offsetWidth}if (top >= midImg.offsetHeight - mask.offsetHeight) {top = midImg.offsetHeight - mask.offsetHeight}// 根据top和left调整mask的位置mask.style.left = left + 'px';mask.style.top = top + 'px';// 根据top 和 left,修改大图的位置,background-position-xlargeImg.style.backgroundPositionX = -left + 'px';largeImg.style.backgroundPositionY = -top + 'px';}鼠标移出
midImg.onmouseleave = function (e) {// 让遮罩层和大图消失mask.style.opacity = 0;largeImg.style.opacity = 0;}
🌟完整代码
最后在把js代码进行整理与二次封装,完整代码如下:
index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><link rel="stylesheet" href="./index.css">
</head>
<body><!--最外层容器 --><div class="container"><!-- 左侧原图 --><div class="left-img"><!-- 遮罩层 --><div class="mask"></div></div><!-- 右侧放大图片 --><div class="right-img"></div><!-- 缩略图集合 --><div class="img-list-wrapper"><ul class="img-list"></ul></div></div><script src="./index.js"></script>
</body>
</html>
index.css
* {margin: 0;padding: 0;list-style: none;
}.container {width: 1000px;height: 600px;margin: 50px auto;font-size: 0;
}.left-img {width: 490px;height: 510px;margin-right: 16px;border: 1px solid #eee;display: inline-block;/* 图片 */background-image: url(./images/imgA_2.jpg);background-repeat: no-repeat;background-position: center;background-size: cover;/* 遮罩层相对我进行定位 */position: relative;
}
.mask {width: 230px;height: 230px;background-image: url(./images/bg.png);position: absolute;top: 0;left: 0;opacity: 0;
}
.right-img {width: 490px;height: 510px;border: 1px solid #eee;display: inline-block;background-image: url(./images/imgA_3.jpg);background-repeat: no-repeat;opacity: 0;
}.img-list-wrapper {width: 490px;text-align: center;margin-top: 10px;
}
.img-list {display: inline-block;
}
.img-list li {display: inline-block;width: 60px;height: 60px;margin: 0 5px;cursor: pointer;background-image: url(./images/imgA_1.jpg);background-repeat: no-repeat;/* border: 2px solid #000; */border: 1px solid #eee;
}
index.js
// 封装一个获取DOM元素的方法// 单一元素
function $(selector) {return document.querySelector(selector);
}// 多个元素
function $$(selector) {return document.querySelectorAll(selector);
}// 初始化数据图片
let imgs = {// 小图small: ['imgA_1.jpg', 'imgB_1.jpg', 'imgC_1.jpg'],// 中图middle: ['imgA_2.jpg', 'imgB_2.jpg', 'imgC_2.jpg'],// 大图large: ['imgA_3.jpg', 'imgB_3.jpg', 'imgC_3.jpg']
}// 获取一些将要使用的dom元素
let container = $('.container');
let largeImg = $('.right-img');
let midImg = $('.left-img');
let smallImg = $('.img-list');
let mask = $('.mask');// 两大类事件类型, 点击, 鼠标移入移出
// 1. 点击事件, 事件委托
smallImg.onclick = function (e) {// 判断我点击的元素是li元素if (e.target.tagName == 'LI') {// 让所有li元素取消borderlet lis = $$('li');for (let i = 0; i < lis.length; i++) {lis[i].style.border = 'none';}// 让选中的li元素添加bordere.target.style.border = '2px solid #000';// 点击缩略图后,原图和大图也需要跟着变换,// 点的是第几个元素, 获取元素索引// [1,2,3].indexOf(3) == 2let index = [].indexOf.call(lis, e.target);midImg.style.backgroundImage = 'url(./images/' + imgs.middle[index] + ')';largeImg.style.backgroundImage = 'url(./images/' + imgs.large[index] + ')';}
}// 函数封装,一键启动
// 函数本质:若干步骤的集合
// 初始化页面函数
function initPage() {var str = '';for (let i = 0; i < imgs.small.length; i++) {str += '<li style="background-image: url(./images/' + imgs.small[i] + ');"></li>'}smallImg.innerHTML = str;// 2. 默认选中第一个缩略图$('.img-list li').style.border = '2px solid #000';
}// 绑定事件
function bindEvent() {midImg.onmousemove = function (e) {// 让遮罩层和大图展示mask.style.opacity = 1;largeImg.style.opacity = 1;// 根据鼠标位置计算遮罩层的位置let left = e.clientX - midImg.offsetLeft - mask.offsetWidth / 2;// 同理let top = e.clientY - midImg.offsetTop - mask.offsetHeight / 2;// 边界条件if (left <= 0) {left = 0;}if (top <= 0) {top = 0;}if (left >= midImg.offsetWidth - mask.offsetWidth) {left = midImg.offsetWidth - mask.offsetWidth}if (top >= midImg.offsetHeight - mask.offsetHeight) {top = midImg.offsetHeight - mask.offsetHeight}// 根据top和left调整mask的位置mask.style.left = left + 'px';mask.style.top = top + 'px';// 根据top 和 left,修改大图的位置,background-position-xlargeImg.style.backgroundPositionX = -left + 'px';largeImg.style.backgroundPositionY = -top + 'px';}// 2. 移出midImg.onmouseleave = function (e) {// 让遮罩层和大图消失mask.style.opacity = 0;largeImg.style.opacity = 0;}
}// 一键启动,执行一个函数
function main() {initPage();bindEvent();
}main();
🌟写在最后
本专栏将持续更新原生JS案例,提供一些工作中也能用上的一些小案例,详细讲解分析,提升JS开发水平与开发思路的积累,如果文中出现有瑕疵的地方各位通过评论或者私信联系我,我们一起进步,有兴趣的伙伴可以订阅一下:点击关注JS经典案例专栏

相关文章:
【JS案例】JS实现图片放大镜功能
JS案例图片放大镜 🌟效果展示 🌟HTML结构 🌟CSS样式 🌟实现思路 🌟具体实现 1.初始化数据图片 2.获取所需DOM元素 3.初始化页面 初始化缩略图 绑定事件 🌟完整代码 🌟写在最后 &…...
linux centos7 bash中字符串反向输出
给定一个字符串,如何反向(倒序)输出? 字符串反转的方法:a.对各个字符位置进行循环调换(从原字符串左边取出放在新字符串的右边;从原字符串右边取出放在新字符串的左边)。b.对各个字符由水平排列转为垂直排…...
git rebase和merge区别
一、概述 merge和rebase 标题上的两个命令:merge和rebase都是用来合并分支的。 这里不解释rebase命令,以及两个命令的原理,详细解释参考这里。 下面的内容主要说的是两者在实际操作中的区别。 1.1 什么是分支 分支就是便于多人在同一项目…...
Vue插槽实现商品列表-编辑渲染
商品列表 文章目录 商品列表核心步骤创建组件 1. MyTag组件详细步骤双击显示,自动聚焦失去焦点,隐藏输入框回显标签信息回车修修改内容,同时隐藏输入框 MyTable组件详细步骤1-动态的设置整个表格的数据 : props2-实现自定义结构-插…...
Vue开发之父子组件
创建父子组建,分三步。一是创建文件,二是引入组建,三是组件间通信。在components目录下新建sub文件夹,用于存放一下可以复用的子组件。比如新建一个SubCon.vue组件 <template><div class"first-app">{{ ms…...
fastadmin think-queue supervisor配置
起因是微信支付回调需要同时做发货处理,但是发货接口不能影响,需要队列进行异步处理1. 1.fastadmin 后台购买queue插件(基于think-queue消息队列) 2.代码 2.1 添加文件:application---->extra--->queue.php 内容:我这里用的数据库做…...
STM32 进不了main 函数
1. 我用的是STM32L151C8T6 的芯片,在github 上找了个别人的例程,拿来当模板改,由于他用的是HSE 外部晶振,我用的是内部晶振HSI,所以需要改系统时钟,改完后debug, 一直进不了main 函数࿰…...
不用循环数组,js+html实现贪吃蛇
功能描述:每走10步随机改变一个方方向,当键盘按下方向键 w,s,a,d时,使用键盘方向控制蛇的移动,蛇头每撞到一次自身时改变屏幕颜色,蛇头碰到边界时从另一边回来。 实现思路:用个30大小的数组存放每个结点&a…...
什么是线程安全和线程不安全?
线程安全(Thread Safety)和线程不安全(Thread Unsafety)是与并发编程相关的概念,特别是在多线程环境中使用共享资源时会涉及到这些概念。 线程安全: 当多个线程同时访问共享资源时,如果在没有额外的同步措施的情况下,这些线程仍然能够正确地执行并保持数据的一致性,那…...
VUE笔记(十)Echarts
一、Echarts简介 1、什么是echarts ECharts是一款基个基于 JavaScript 的开源可视化图表库 官网地址:Apache ECharts 国内镜像:ISQQW.COM x ECharts 文档(国内同步镜像) - 配置项 示例:echarts图表集 2、第一个E…...
FPGA原理与结构——时钟IP核原理学习
一、前言 在之前的文章中,我们介绍了FPGA的时钟结构 FPGA原理与结构——时钟资源https://blog.csdn.net/apple_53311083/article/details/132307564?spm1001.2014.3001.5502 在本文中我们将学习xilinx系列的FPGA所提供的时钟IP核,来帮助我们进一…...
创建python环境——Anaconda
在Windows中安装Anaconda和简单使用 一.Anaconda发行概述 Anaconda是一个可以便捷获取和管理包,同时对环境进行统一管理的发行版本,它包含了conda、 Python在内的超过180个科学包及其依赖项。 1.Anaconda发行版本具有以下特点: (1)包含了…...
使用Linux部署Kafka教程
目录 一、部署Zookeeper 1 拉取Zookeeper镜像 2 运行Zookeeper 二、部署Kafka 1 拉取Kafka镜像 2 运行Kafka 三、验证是否部署成功 1 进入到kafka容器中 2 创建topic 生产者 3 生产者发送消息 4 消费者消费消息 四、搭建kafka管理平台 五、SpringBoot整合Kafka 1…...
pyechart笔记:opts.AxisOpts
定制化图表的轴线(x轴和y轴)的样式和设置 0 不设置坐标轴 c1(Bar().add_xaxis([力量,智力,敏捷]).add_yaxis(全能骑士,# 系列名称,用于 tooltip 的显示,legend 的图例筛选。[429,321,296],#系列数据).add_yaxis(猴子,[352,236,4…...
深度思考rpc框架面经之五:rpc熔断限流、rpc复用连接机制
11 RPC框架如何实现限流和熔断 推荐文章:RPC实现原理之核心技术-限流熔断 11.1 为什么Dubbo要做服务的限流?(根本原因是服务端进行自我保护) 限流是一种常见的系统保护手段。在分布式系统和微服务架构中,一个接口的过度使用可能会导致资源…...
Go 数组
数组用于在单个变量中存储相同类型的多个值,而不是为每个值声明单独的变量。 声明数组 在Go中,有两种声明数组的方式: 使用var关键字: 语法 var array_name [length]datatype{values} // 这里定义了长度 或者 var array_n…...
04架构管理之分支管理实践-一种git分支管理最佳实践
专栏说明:针对于企业的架构管理岗位,分享架构管理岗位的职责,工作内容,指导架构师如何完成架构管理工作,完成架构师到架构管理者的转变。计划以10篇博客阐述清楚架构管理工作,专栏名称:架构管理…...
D.OASIS City 和 Warrix 在The Sandbox 庆祝 Rise of the 10th Legend十周年
D.OASIS 首次展示了变革性娱乐 D.OASIS City,正如它与 WARRIX 一起承诺的那样。WARRIX 是获得泰国国家队球衣生产授权的标志性运动服装品牌。 这款激动人心的游戏冒险游戏于今天推出,让用户能够投入 D.OASIS City x WARRIX:Rise of the 10th…...
Git基本操作(Idea版)
第一次发布项目(本地->远程) 方式一 通过push的方式推送本地库到远程库(远程已创建好仓库) 这种方式需要提前创建好仓库。 右键点击项目,可以将当前分支的内容 push 到 GitHub 的远程仓库中。 注意:…...
idea大量爆红问题解决
问题描述 在学习和工作中,idea是程序员不可缺少的一个工具,但是突然在有些时候就会出现大量爆红的问题,发现无法跳转,无论是关机重启或者是替换root都无法解决 就是如上所展示的问题,但是程序依然可以启动。 问题解决…...
云原生核心技术 (7/12): K8s 核心概念白话解读(上):Pod 和 Deployment 究竟是什么?
大家好,欢迎来到《云原生核心技术》系列的第七篇! 在上一篇,我们成功地使用 Minikube 或 kind 在自己的电脑上搭建起了一个迷你但功能完备的 Kubernetes 集群。现在,我们就像一个拥有了一块崭新数字土地的农场主,是时…...
抖音增长新引擎:品融电商,一站式全案代运营领跑者
抖音增长新引擎:品融电商,一站式全案代运营领跑者 在抖音这个日活超7亿的流量汪洋中,品牌如何破浪前行?自建团队成本高、效果难控;碎片化运营又难成合力——这正是许多企业面临的增长困局。品融电商以「抖音全案代运营…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
学校时钟系统,标准考场时钟系统,AI亮相2025高考,赛思时钟系统为教育公平筑起“精准防线”
2025年#高考 将在近日拉开帷幕,#AI 监考一度冲上热搜。当AI深度融入高考,#时间同步 不再是辅助功能,而是决定AI监考系统成败的“生命线”。 AI亮相2025高考,40种异常行为0.5秒精准识别 2025年高考即将拉开帷幕,江西、…...
docker 部署发现spring.profiles.active 问题
报错: org.springframework.boot.context.config.InvalidConfigDataPropertyException: Property spring.profiles.active imported from location class path resource [application-test.yml] is invalid in a profile specific resource [origin: class path re…...

