功能:
1、鼠标移动区域,图片局部放大
2、点击左右按钮可以进行图片切换
3、鼠标悬停到图片上面,下面小图和主图自动切换
效果图:
完整代码:
<template>
<div
class="productCarousels"
:style="width"
@mouseleave="flag = false"
>
<div
class="exhibitionArea"
:class="{noPadding:productImgArr.length===0}"
@mouseenter="animation"
@mouseleave="mouseleave"
>
<img v-if="productImgArr.length!==0 && !props.localAmplifica" :src="productImgArr[currentIndex]" class="nolocalAmplifica">
<div
v-if="props.localAmplifica && isHaveData"
class="imgBigShow"
:style="{width:imgBox.width+'px',height:imgBox.height+'px'}"
ref="moveDom"
>
<img
v-if="productImgArr.length!==0" :src="productImgArr[currentIndex]"
:class="{ imgBorder: flag }"
class="moveImgDom"
>
<div
v-if="flag"
class="moveBorder"
:style="{
width:moveBorder.width+'px',
height:moveBorder.height+'px',
left: styleObj.left + 'px',
top: styleObj.top + 'px',
}"
></div>
</div>
<h4 v-if="isShowItem&&productImgArr.length===0" style="margin: 60px auto;color: #909399;">暂无图片</h4>
<div class="noImgData" v-if="!isShowItem">
<div>{{props.title}}</div>
<img :src="bj_t" alt="">
</div>
<div class="leftNav" v-if="productImgArr.length>1" title="上一张" @click="last"><img :src="zb"></div>
<div class="rightNav" v-if="productImgArr.length>1" title="下一张" @click="next"><img :src="yb"></div>
</div>
<div class="imgItem" v-if="isShowItem">
<div
v-if="productImgArr.length!==0"
v-for="(item,index) in productImgArr"
:key="index"
class="imgItemBox"
:class="{currentItem: currentIndex==index,lastImg:animate}"
@mouseenter="currentPto(index)"
><img :src="item"></div>
<h4 v-if="productImgArr.length===0" style="margin: 0 auto;color: #909399;">该商家尚未上传设备!</h4>
</div>
<div class="pop-image" v-if="flag" :style="{width:popImageBox.width + 'px',height:popImageBox.height + 'px',left: popImageBox.left + 'px'}">
<img
:src="productImgArr[currentIndex]" alt=""
:style="{
width:popImageStyle.width + 'px',
height:popImageStyle.height + 'px',
top:'-'+(styleObj.top*4) + 'px',
left:'-'+(styleObj.left*4) + 'px',
}"
>
</div>
</div>
</template>
<script setup name="productCarousels">
import zb from '@/assets/images/device/zb.png'
import yb from '@/assets/images/device/yb.png'
import bj_t from '@/assets/images/device/bj_t.png'
import {nextTick, reactive, watch} from "vue";
import {onMounted, onBeforeUnmount, ref} from "vue";
import {useRouter} from "vue-router";
const router = useRouter();
let props = defineProps({
height:{
type:String,
default:'240'
},
productImgArr:{
type:Array,
default: [] // 图片链接的数组
},
isShowItem:{ // 是否显示下面的子项图片列表
type:Boolean,
default:true
},
title:{
type:String,
default:''
},
localAmplifica:{ // 图片是否局部放大
type:Boolean,
default:false
},
autoSwitch:{ // 鼠标放上去时,图片是否自动轮播
type:Boolean,
default:true
}
})
let width = {
width: '355px',
margin: '0 auto'
}
let currentIndex = ref(0)
let animate = ref(false)
let timer1 = ref('')
let timer2 = ref('')
let timer3 = ref('')
let timer4 = ref('')
let animateORclick = ref(0)
function last(){
mouseleave()
animation()
if (currentIndex.value===0){
// 处理当图片大于6张时,点击下一张导致看不到当前是哪一项图片问题,做循环处理
currentIndex.value = 0
props.productImgArr.unshift(props.productImgArr[props.productImgArr.length-1])
props.productImgArr.pop()
return
}
currentIndex.value--
}
function next(){
mouseleave()
animation()
if (currentIndex.value===props.productImgArr.length-1){
currentIndex.value = 0
return
}
currentIndex.value++
if (currentIndex.value>5){
// 处理当图片大于6张时,点击下一张导致看不到当前是哪一项图片问题,做循环处理
currentIndex.value = 5
controlOrder(props.productImgArr)
}
}
const currentPto = (index) => currentIndex.value = index
function mouseleave(){
window.clearInterval(timer1.value)
window.clearInterval(timer2.value)
window.clearInterval(timer4.value)
stopTimer3()
}
function animation() {
if (!props.autoSwitch) return;
if (props.productImgArr.length==0) return;
timer3.value = window.setInterval(()=>{
animateORclick.value++
},1000)
}
function stopTimer3(){
animateORclick.value = 0
window.clearInterval(timer3.value)
}
function controlOrder(data){
data.push(data[0])
data.shift()
animate.value = false
}
// 监听数据详情切换,重置图片下标
watch(()=>router.currentRoute.value.params.mixid,newValue=>{ currentIndex.value = 0 })
watch(()=>animateORclick.value,(newValue)=>{
if (newValue==3&&props.productImgArr.length>6){
animate.value = true
timer2.value = setTimeout(() => {
controlOrder(props.productImgArr)
}, 900)
}else if (newValue>3&&props.productImgArr.length>6){
mouseleave()
timer1.value = setInterval(() => {
animate.value = true
timer2.value = setTimeout(() => {
controlOrder(props.productImgArr)
}, 1200)
}, 2000)
} else if (props.productImgArr.length<=6){
mouseleave()
timer4.value = window.setInterval(()=>{
if (currentIndex.value===props.productImgArr.length-1){
currentIndex.value = 0
return
}
currentIndex.value++
},2000)
}
})
// -----------------移动局部放大图片逻辑 start
const moveDom = ref(null) // 需要放大的图片
const flag = ref(false)
const styleObj = ref({ left: 0, top: 0 })
let imgBox = ref({ width: 275, height: 191.781 })
let moveBorder = ref({ width: 0, height: 0 })
let popImageBox = ref({ width: 0, height: 0, left: 0 })
let popImageStyle = ref({ width: 0, height: 0 })
let timerD1 = ref(null)
let timerD2 = ref(null)
let isHaveData = ref(false)
watch(()=>props.productImgArr,(newValue) => {
isHaveData.value = newValue&&newValue.length>0
} ,{ deep: true })
onMounted(() => {
timerD1.value = setTimeout(()=>{
if (props.localAmplifica && isHaveData.value){
moveDom.value.addEventListener('mousemove', moveEvent)
moveDom.value.addEventListener('mouseleave', mouseleaveEvent)
}
},500)
})
onBeforeUnmount(() => {
window.clearTimeout(timer2.value)
window.clearTimeout(timerD1.value)
window.clearTimeout(timerD2.value)
if (moveDom.value){
moveDom.value.removeEventListener('mouseleave', mouseleaveEvent)
moveDom.value.removeEventListener('mousemove', moveEvent)
}
})
const rafThrottle = (fn) => {
let locked = false;
return function (...args) {
if (locked) return;
locked = true;
window.requestAnimationFrame(_ => {
fn.apply(this, args);
locked = false;
});
};
}
// 计算每个框元素的大小
function calculationDate(){
let moveImgDom = window.getComputedStyle(document.querySelector('.moveImgDom'))
let exhibitionArea = window.getComputedStyle(document.querySelectorAll('.exhibitionArea')[0]).width.split('px')[0]
imgBox.value = {
width: moveImgDom.width.split('px')[0],
height: moveImgDom.height.split('px')[0]
}
moveBorder.value = {
width: moveImgDom.width.split('px')[0] / 2,
height: moveImgDom.height.split('px')[0] / 2
}
popImageBox.value = {
width: imgBox.value.width*2,
height: imgBox.value.height*2,
left:Number(exhibitionArea) + 25
}
popImageStyle.value = {
width: popImageBox.value.width*2,
height: popImageBox.value.height*2,
}
}
function resetFDdata(){
imgBox.value = { width: 275, height: 191.781 }
moveBorder.value = { width: 0, height: 0 }
popImageBox.value = { width: 0, height: 0, left:0 }
popImageStyle.value = { width: 0, height: 0 }
styleObj.value = { left: 0, top: 0 }
}
watch(()=>currentIndex.value,async ()=>{
// 每切换一张图片,数据就得重新计算,为了更好的进行局部放大效果
if (props.localAmplifica){ // 只有支持图片局部放大功能才做如下处理
await resetFDdata();
await calculationDate();
}
})
// 鼠标移动事件
const moveEvent = rafThrottle(e => {
timerD2.value = setTimeout(()=>{
nextTick( async ()=>{
try {
await calculationDate();
let event = e || window.event;
if (event.preventDefault) {
event.preventDefault();
} else {
event.returnValue = false;
}
flag.value = true
let { offsetX, offsetY } = e;
let { height, width } = imgBox.value
//计算边界条件
styleObj.value = {
left: (offsetX - moveBorder.value.width / 2) > 0 ? (offsetX + moveBorder.value.width / 2) >= width ? width - moveBorder.value.width : offsetX - moveBorder.value.width / 2 : 0,
top: (offsetY - moveBorder.value.height / 2) > 0 ? (offsetY + moveBorder.value.height / 2) >= height ? height - moveBorder.value.height : offsetY - moveBorder.value.height / 2 : 0
}
// let { left, top } = styleObj.value;
//根据比例计算放大的区域
} catch (e) { console.log(e) }
})
})
})
// 鼠标移出
const mouseleaveEvent = rafThrottle(e => {
flag.value = false
})
// -----------------移动局部放大图片逻辑 end
</script>
<style lang="scss" scoped>
* {
margin: 0;
padding: 0;
}
.productCarousels {
box-sizing: border-box;
position: relative;
.exhibitionArea {
width: 315px;
height: 155px;
border-radius: 6px;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 16%);
box-sizing: border-box;
position: relative;
padding: 0 20px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
margin: 0 auto;
&:hover {
.leftNav,
.rightNav {
display: block;
}
}
.noImgData {
position: relative;
width: 100%;
height: 100%;
margin: 0 -20px;
user-select: none;
div {
text-align: center;
position: absolute;
width: 90%;
height: 205px;
line-height: 50px;
overflow: hidden;
left: 0;
bottom: 0;
right: 0;
top: 0;
margin: auto;
z-index: 1;
font-size: 32px;
font-family: "Source Han Serif CN-Medium", "Source Han Serif CN";
font-weight: 500;
color: #fff;
}
img {
position: absolute;
width: 100%;
height: 100%;
}
}
.leftNav,
.rightNav {
width: 30px;
height: 30px;
position: absolute;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
display: none;
img {
width: 100%;
height: 100%;
}
}
.leftNav {
left: 0;
}
.rightNav {
right: 0;
}
.nolocalAmplifica {
position: absolute;
max-width: 100%;
max-height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
.imgBigShow {
position: relative;
width: 275px;
height: 191.781px;
.moveBorder {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
background-color: rgb(150 163 199 / 31.5%);
display: flex;
flex-wrap: wrap;
overflow: hidden;
}
img {
max-width: 100%;
display: block;
padding: 1px;
flex-shrink: 0;
&:hover {
cursor: move;
}
}
}
}
.noPadding {
padding: 0;
}
.imgItem {
height: 60px;
margin-top: 10px;
display: flex;
overflow: hidden;
padding-left: 20px;
padding-top: 7px;
width: 94%;
.imgItemBox {
flex-shrink: 0;
width: 43px;
height: 43px;
box-shadow: 0 3px 6px 1px rgb(0 0 0 / 20%);
border-radius: 6px;
margin-right: 10px;
position: relative;
border: 1px solid #fff;
&:hover {
transform: scale(1.18);
transition-duration: 0.5s;
}
img {
padding: 1px;
max-width: 100%;
max-height: 100%;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
}
}
.pop-image {
width: 315px;
height: 155px;
//border: 1px solid #0ba81a;
position: absolute;
top: 0;
left: 315px;
background-color: white;
overflow: hidden;
img {
position: absolute;
}
}
}
.currentItem {
transform: scale(1.18);
border-radius: 6px;
border-color: #aec9fe !important;
}
.lastImg {
transition: all 2s;
transform: translateX(-60px);
}
// 图片局部放大逻辑 start
.imgBorder {
border: 1px solid #ddd;
}
// 图片局部放大逻辑 end
</style>
不理解,不懂的,欢迎留言