放大镜的构造函数
类名:
box: 整个放大镜区域的盒子
show: 一个 div, 内部是展示的图片
mask: 在 show 的子级元素, 是图片上的遮罩层
list: 下面切换图片的列表盒子
=> list > p > img
enlarge: box 的子级元素, 就是承载放大图片的盒子
需求:
0. 调整比例(选做)
=> 可以自己使用计算器计算, 去调整 css
=> 也可以通过 js 代码来计算出比例, 算出一个尺寸去赋值
1. 移入移出
=> 移入 show 的时候, mask 和 enlarge 显示
=> 移出 show 的时候, mask 和 enlarge 消失
2. 点击切换
=> 点击 list 下面的 p 标签的时候
=> 切换 show 下面 img 标签的 图片
=> 切换 enlarge 盒子的 背景图片
3. 鼠标跟随
=> 当鼠标在 show 内移动的时候
=> mask 跟随移动
=> enlarge 盒子的 backgroundPosition 成比例跟随移动
自己不作为事件目标 // pointer-events: none;
属性:
ele: 范围元素
show: 显示图片的盒子
list: 切换列表的盒子
mask: 遮罩层盒子
enlarge: 放大镜盒子
show 盒子的尺寸
mask 盒子的尺寸
背景图的尺寸
方法:
调整比例
移入移出
列表切换
跟随移动
function Enlarge(select) {
// select 表示范围元素的 选择器
this.ele = document.querySelector(select)
// show 盒子
this.show = this.ele.querySelector('.show')
// mask 盒子
this.mask = this.ele.querySelector('.mask')
// list 盒子
this.list = this.ele.querySelector('.list')
// enlarge 盒子
this.enlarge = this.ele.querySelector('.enlarge')
// show 的宽度
// 元素.offsetWidth 获取的是元素 内容 + padding + border 区域的尺寸
// 注意: 当元素 display: none; 的时候获取不到尺寸
this.show_width = this.show.offsetWidth
// show 的高度
this.show_height = this.show.offsetHeight
// mask 的宽度
// 获取元素非行内样式
// window.getComputedStyle(元素).样式名
this.mask_width = parseInt(window.getComputedStyle(this.mask).width)
// mask 的高度
this.mask_height = parseInt(window.getComputedStyle(this.mask).height)
// 背景图 的宽度
// 获取元素非行内样式
this.bg_width = parseInt(window.getComputedStyle(this.enlarge).backgroundSize.split(' ')[0])
this.bg_height = parseInt(window.getComputedStyle(this.enlarge).backgroundSize.split(' ')[1])
// 调用启动器就可以了
this.init()
}
// 0. 启动器方法
Enlarge.prototype.init = function () {
this.setScale()
this.overOut()
this.changeList()
this.move()
}
// 1. 调整比例
/*
已知:
show 的尺寸
mask 的尺寸
背景图 的尺寸
show 盒子尺寸 背景图尺寸
--------------- = ------------------
mask 盒子尺寸 enlarge 盒子尺寸
公式变形: 背景图尺寸 * mask 盒子尺寸 = enlarge 盒子尺寸 * show 盒子尺寸
公式变形: enlarge 盒子尺寸 = 背景图尺寸 * mask 盒子尺寸 / show 盒子尺寸
*/
Enlarge.prototype.setScale = function () {
// 1-1. 计算出 enlarge 盒子的尺寸
this.enlarge_width = this.bg_width * this.mask_width / this.show_width
this.enlarge_height = this.bg_height * this.mask_height / this.show_height
// 1-2. 给 this.enlarge 盒子赋值
this.enlarge.style.width = this.enlarge_width + 'px'
this.enlarge.style.height = this.enlarge_height + 'px'
}
// 2. 移入移出
/*
this.show 绑定移入移出事件, 操作两个元素显示隐藏
*/
Enlarge.prototype.overOut = function () {
// 2-1. 绑定鼠标移入事件
this.show.addEventListener('mouseover', () => {
this.mask.style.display = 'block'
this.enlarge.style.display = 'block'
})
// 2-2. 绑定鼠标移出事件
this.show.addEventListener('mouseout', () => {
this.mask.style.display = 'none'
this.enlarge.style.display = 'none'
})
}
// 3. 列表切换
/*
问题1: 给谁绑定事件 ?
=> 事件委托, 委托给谁 ?
=> 委托给 list
问题2: 如何判断 target ?
=> 判断 target 是 img 标签
=> 才进行切换
问题3: 把 show 盒子下面的 img 换成什么 ?
=> 把需要用到的图片地址, 以自定义属性的形式写在 img 标签身上
=> 当你点击的时候, 拿到 img 标签身上记录的两个自定义属性就好了
*/
Enlarge.prototype.changeList = function () {
this.list.addEventListener('click', e => {
// 处理事件对象兼容
e = e || window.event
// 处理事件目标兼容
const target = e.target || e.srcElement
// 判断点击的是 img 标签
// 节点属性: 元素节点的 nodeName: 大写标签名
// target是准确触发事件的元素, 如果准确触发事件的元素的标签名是 IMG, 那么证明我点击的是 要切换的图片
// 执行切换的操作
if (target.nodeName === 'IMG') {
const show_url = target.dataset.show
const enlarge_url = target.dataset.enlarge
// 更换地址
this.show.firstElementChild.src = show_url
this.enlarge.style.backgroundImage = `url(${ enlarge_url })`
// 更换 p 标签的 active 类名
// 给 list 的所有子元素取消 active
for (let i = 0; i < this.list.children.length; i++) {
this.list.children[i].classList.remove('active')
}
target.parentElement.classList.add('active')
}
})
}
// 4. 跟随移动
/*
问题1: 给谁绑定鼠标移动事件 ?
=> this.show 盒子绑定 mousemove 事件
问题2: 获取哪一组坐标 ?
=> page 一组: 相对于文档流左上角
=> client 一组: 相对于浏览器可视窗口左上角
=> offset 一组: 相对于事件目标左上角
=> mask 盒子是根据 show 盒子进行定位的
=> 原因: 因为当光标进入 mask 盒子以后, 事件目标变成了 mask 盒子, 导致拿到的坐标是相对于 mask 的
=> 解决: 当光标进入 mask 盒子以后, 事件目标不是 mask 盒子依旧还是 show 盒子
-> 给 mask 盒子添加一个 css 样式, 叫做 pointer-events: none;
-> 作用: 该元素永远不作为事件目标使用, 所有事件在自己身上发生的时候
-> 直接穿透过自己, 到达结构父级身上
问题3: 是否需要边界值判断 ?
=> 需要
=> this.show 的尺寸就是边界
问题4: 如何设置背景图定位的值 ?
=> 如何计算
mask 移动距离 mask 盒子尺寸
---------------- = ------------------
背景图移动距离 enlarge 盒子尺寸
公式变形: 背景图移动距离 * mask 盒子尺寸 = mask 移动距离 * enlarge 盒子尺寸
公式变形: 背景图移动距离 = mask 移动距离 * enlarge 盒子尺寸 / mask 盒子尺寸
*/
Enlarge.prototype.move = function () {
// 4-1. 给 this.show 绑定事件
this.show.addEventListener('mousemove', e => {
// 处理事件对象兼容
e = e || window.event
// 4-2. 拿到坐标点
let x = e.offsetX - this.mask_width / 2
let y = e.offsetY - this.mask_height / 2
// 4-3. 边界值判断
if (x <= 0) x = 0
if (y <= 0) y = 0
if (x >= this.show_width - this.mask_width) x = this.show_width - this.mask_width
if (y >= this.show_height - this.mask_height) y = this.show_height - this.mask_height
// 4-4. 给 this.mask 进行 left 和 top 的赋值
this.mask.style.left = x + 'px'
this.mask.style.top = y + 'px'
// 4-5. 让 enlarge 盒子的背景图片定位跟随成比例联动
// 公式: 背景图移动距离 = mask 移动距离 * enlarge 盒子尺寸 / mask 盒子尺寸
const bgx = x * this.enlarge_width / this.mask_width
const bgy = y * this.enlarge_height / this.mask_height
// 4-6. 给 enlarge 盒子的 backgroundPosition 赋值
this.enlarge.style.backgroundPosition = `-${ bgx }px -${ bgy }px`
})
}
<!-- 整个的放大镜区域 -->
<div class="box" id="box">
<!-- 正常展示的图片 -->
<div class="show">
<img src="./imgs/1.jpg" alt="">
<!-- 遮罩层盒子 -->
<div class="mask"></div>
</div>
<!-- 切换图片的列表 -->
<div class="list">
<p class="active">
<img
src="./imgs/1.small.jpg"
data-show="./imgs/1.jpg"
data-enlarge="./imgs/1.big.jpg"
alt="">
</p>
<p>
<img
src="./imgs/2.small.jpg"
data-show="./imgs/2.jpg"
data-enlarge="./imgs/2.big.jpg"
alt="">
</p>
</div>
<!-- 放大镜的盒子 -->
<div class="enlarge"></div>
</div>
<script src="./index.js"></script>
<script>
// 引入文件以后 new 一下就实现了放大镜
const e = new Enlarge('#box')
console.log(e)
</script>
* {
margin: 0;
padding: 0;
}
img {
width: 100%;
height: 100%;
display: block;
}
.box {
width: 450px;
height: 600px;
border: 1px solid #333;
margin: 50px;
display: flex;
flex-direction: column;
position: relative;
}
.box > .show {
width: 450px;
height: 450px;
position: relative;
}
.box > .show > .mask {
width: 200px;
height: 200px;
background-color: yellow;
opacity: 0.5;
position: absolute;
left: 100px;
top: 100px;
/* 自己不作为事件目标 */
pointer-events: none;
display: none;
}
.box > .list {
flex: 1;
border-top: 1px solid #333;
box-sizing: border-box;
padding: 20px;
display: flex;
align-items: center;
}
.box > .list > p {
margin-right: 30px;
border: 1px solid #333;
width: 54px;
height: 54px;
cursor: pointer;
}
.box > .list > p.active {
border-color: orange;
}
.box > .enlarge {
width: 400px;
height: 400px;
position: absolute;
left: 110%;
top: 0;
background-image: url(./imgs/1.big.jpg);
background-size: 800px 800px;
display: none;
}