先看效果,鼠标移入就可以放大, 默认是在原图放大,也可以配置放大的窗口大小,如果图片靠左边, 放大的窗口就往右边靠, 图片在右边,就往左边靠
b站演示:vue3 自定义指令实现图片放大_哔哩哔哩_bilibili
原理:在dom鼠标移入的时候 ,就添加一个div, 完全覆盖住原图, 并把原图的src设置为背景
已经上传npm
npm:tanyupeng - npm
源码如下
//用来判断一个dom元素在可视范围内是否显示完整,不完整放大镜就和img底部对齐
function isElementPartiallyInViewport(element) {
let rect = element.getBoundingClientRect();
let windowHeight = (window.innerHeight || document.documentElement.clientHeight);
let vertInView = (rect.top <= windowHeight) && ((rect.top + rect.height) >= 0) && (rect.bottom > windowHeight);
return vertInView;
}
const enterHandler = (e) => {
const el = e.target
//得到父盒子并加上相对定位,为什么?因为我的放大镜是通过父亲相对定位覆盖图片的
const father = el.parentNode
father.style.position = 'relative'
//寻找放大镜div, 没有的话就创建
let show = document.querySelector('.show')
if (!show) {
//如果没有放大镜蒙版就创建一个
show = document.createElement('div')
//宽高等于图片大小, 因为要覆盖,如果不等于的话就计算大小
let width = el.width
let height = el.height
//给放大镜的宽高设置为原图大小,如果传入倍数就放大倍数
show.style.width = width * el.zoom + 'px'
show.style.height = height * el.zoom + 'px'
//一定要把放大镜的优先级提高
show.style.zIndex = 99
//计算出img距离顶部和左边多少,从而div覆盖原图实现放大效果
let top = el.offsetTop;
let left = el.offsetLeft;
//做一下放大倍数蒙版的适配,让放大镜和原图中心线对齐
// console.log(top + height);
// console.log(window.innerHeight);
if (el.zoom != 1) {
left = left - (width * el.zoom - width) / 2
show.style.left = `${left}px`
show.style.top = `${top}px`
//如果超出左边就右靠
if (left < 0) {
left = el.offsetLeft;
show.style.left = `${left}px`
// show.style.top = `${top}px`
}
//如果超出右边就让他左靠
else if ((left + width * el.zoom) > document.body.offsetWidth) {
show.style.left = el.offsetLeft - (width * el.zoom - width) + 'px'
// show.style.top = `${top}px`
}
//适配top
//如果dom元素在y轴方向显示不完整, 也就是显示一半,那放大镜和原题img以底部对齐
if (isElementPartiallyInViewport(el)) {
show.style.top = top - (height * el.zoom - height) + 'px'
// console.log('不完整');
} else {
// console.log("元素在y轴方向显示完整");
}
} else {
//正常情况下,也就是不放大的情况
show.style.left = `${left}px`
show.style.top = `${top}px`
}
//把img的src取出来给放大镜div做背景
show.style.backgroundImage = `url(${el.src})`
show.style.backgroundRepeat = 'no-repeat'
// show.style.backgroundColor = 'red'
//稍微设置一下圆角
show.style.borderRadius = '3%';
//绝对定位
show.style.position = 'absolute'
// show.style.border = "1px solid rgba(44,163,221,.6)";
//把放大镜添加到father里面
father.appendChild(show)
//放大镜出现就把原来的隐藏掉,主要是放大镜有一个圆角, 不隐藏会不美观
el.style.visibility = 'hidden'
//放大镜移动
show.addEventListener('mousemove', (e) => {
//核心代码
let rect = e.target.getBoundingClientRect()
let x = e.offsetX / rect.width
let y = e.offsetY / rect.height
show.style.backgroundPosition = `calc(${x}*100%) calc(${y}*100%)`
})
show.addEventListener('mouseleave', (e) => {
//让原图又出现
el.style.visibility = 'visible'
if (show) {
//销毁dom, 事件也就不存在了
father.removeChild(show);
}
})
}
}
export default {
mounted(el, bindings) {
//如果有传入放大蒙版配置的话
if (bindings.value && bindings.value.zoom) {
el.zoom = bindings.value.zoom
} else {
//默认是原图放大
el.zoom = 1
}
el.addEventListener('mouseenter', enterHandler)
},
beforeUnmount(el) {
el.removeEventListener('mouseenter', enterHandler)
}
}