部署了一个DEMO,先来看看效果👇
二选一
https://bilibili-banner-qsvs1m9vv-ifavcodes-projects.vercel.app/
https://www.guetzjb.cn/bilibili_banner/#/Bilibili
大致思路:
- 鼠标移入记录一个X偏移,赋值给enterX变量
- 鼠标移动计算当前鼠标x位置和enterX的差值
- 根据算出来的差值偏移图片
- 设计一个偏移阈值,让多张图片移动产生视觉偏差,同时管理好图层顺序。
图片资源可以F12—网络—图片里抓取下载。
2024-07-29 banner图
定义好格式,背景和装饰元素分开,因为背景偏移少一些
{
url: getAssetsImages('xxx.webp'),
offsetThreshold: 10,// 偏移阈值(越大,左右移动越快)
}
图片在src/assets里面(bg1 ~ bg18)
为什么要分背景元素和装饰元素?
看下面这个图
背景元素比视图层大一些,因为这是要左右滑动的,而装饰元素则没有这个限制,怎么移动都可以。
看代码实现蛮简单的~包会
<script lang="ts" setup>
// 获取本地图片
function getAssetsImages(name: string) {
return new URL(`/src/assets/${name}`, import.meta.url).href;
}
// 最外层元素
const eleRef = ref()
// 背景图,下面的会盖住上面的
const bgList = ref([
{
url: getAssetsImages('bg2.webp'),
offsetThreshold: 10,
},
{
url: getAssetsImages('bg7.webp'),
offsetThreshold: 90,
},
{
url: getAssetsImages('bg11.webp'),
offsetThreshold: 95,
},
{
url: getAssetsImages('bg14.webp'),
offsetThreshold: 80,
},
{
url: getAssetsImages('bg9.webp'),
offsetThreshold: 50,
},
{
url: getAssetsImages('bg1.webp'),
offsetThreshold: 90,
},
])
// 装饰元素
const imgList = ref([
{
url: getAssetsImages('bg15.webp'),
offsetThreshold: 70,
},
{
url: getAssetsImages('bg16.webp'),
offsetThreshold: 90,
},
{
url: getAssetsImages('bg17.webp'),
offsetThreshold: 30,
},
{
url: getAssetsImages('bg3.webp'),
offsetThreshold: 150,
},
{
url: getAssetsImages('bg4.webp'),
offsetThreshold: 50,
},
{
url: getAssetsImages('bg5.webp'),
offsetThreshold: 60,
},
{
url: getAssetsImages('bg6.webp'),
offsetThreshold: 100,
},
{
url: getAssetsImages('bg8.webp'),
offsetThreshold: 80,
},
{
url: getAssetsImages('bg10.webp'),
offsetThreshold: 60,
},
{
url: getAssetsImages('bg12.webp'),
offsetThreshold: 20,
},
{
url: getAssetsImages('bg13.webp'),
offsetThreshold: 10,
},
{
url: getAssetsImages('bg18.webp'),
offsetThreshold: 30,
},
])
// 背景平移量
const bgTransform = ref<string[]>([])
// 装饰元素平移量
const imgTransform = ref<string[]>([])
const isEnter = ref<boolean>(false) // 鼠标是否移入
// 首次进入的X坐标
let enterX = 0
const mouseEnter = (e: MouseEvent) => {
initPosition()
isEnter.value = true
enterX = e.x
}
const mouseLeave = (_e: MouseEvent) => {
isEnter.value = false
initPosition()
}
const mouseMove = (e: MouseEvent) => {
// (当前x坐标 - 初始x位置) / dom宽度
const xOffset = (e.x - enterX) / eleRef.value.clientWidth * 100
// 背景移动
for (let i = 0; i < bgList.value.length; i++) {
let v = xOffset * (bgList.value[i].offsetThreshold / 100) // 根据偏移阈值计算
bgTransform.value[i] = `translateX(${-100 + v}px)` // 初始偏移量为 100,这样设置可以在[-200,0]之间移动,之后绑定到dom上
}
// 装饰物移动
for (let i = 0; i < imgList.value.length; i++) {
let v = xOffset * (imgList.value[i].offsetThreshold / 100)
imgTransform.value[i] = `translateX(${v}px)`
}
}
// 初始化位置
const initPosition = () => {
bgTransform.value = new Array(bgList.value.length).fill('translateX(-100px)')
imgTransform.value = new Array(imgList.value.length).fill('translateX(0px)')
}
</script>
<template>
<div ref="eleRef" class="relative w-screen max-w-[2480px] h-[155px] overflow-hidden" @mouseenter="mouseEnter"
@mouseleave="mouseLeave" @mousemove="mouseMove">
<!-- 默认所有图片绝对定位在左上角,再利用translateX偏移位置 -->
<!-- 背景元素的宽度比父元素大200px,用于左右偏移 -->
<!-- isEnter为false时,引入过渡动画,还原图片原先的位置 -->
<div :class="isEnter ? '' : 'transition-transform duration-200'" class="absolute left-0 top-0 translate-x-[-100px]"
:style="{ transform: bgTransform[idx], width: eleRef ? eleRef.clientWidth + 200 + 'px' : '100%' }"
v-for="(img, idx) in bgList" :key="idx">
<img class="w-full object-cover" :src="img.url" alt="">
</div>
<!-- 装饰元素 -->
<div :class="isEnter ? '' : 'transition-transform duration-200'" class="absolute left-0 top-0"
:style="{ transform: imgTransform[idx] }" v-for="(img, idx) in imgList" :key="idx + bgList.length">
<img :src="img.url" alt="">
</div>
</div>
</template>
<style scoped></style>