看到一些游戏下载站的顶部会有一个游戏图标滚动的效果,以下是一个简易版的实现(基于vu3)。有需要的boy可以作为参考自己再行封装。
原文出处:https://andomahoro.top/#/view/14
效果如下图所示
代码如下:
<template>
<div ref="carouselRef" class="card-carousel-out">
<div class="card-carousel" :style="{width:prettyWidth+'px',}">
<div class="card-carousel--overflow-container">
<div class="card-carousel-cards" :style="{ transform: 'translateX' + '\(' + currentOffset + 'px' + '\)',}">
<div v-for="item in listData" class="card-carousel--card" :style="{margin:'0 ' + marginAmount +'px'}">
<div :style="{width:elWidth + 'px',}">
<img :src="item.icon"/>
<div>{{item.name}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="mt-2">
<el-button @click="moveCarousel(-1)" :disabled="atHeadOfList">left</el-button>
<el-button @click="moveCarousel(1)" :disabled="atEndOfList">right</el-button>
</div>
<div>{{output}}</div>
</template>
<script setup>
import { ref,reactive,computed,watch,nextTick,getCurrentInstance,onMounted,onBeforeUnmount} from 'vue'
const carouselRef = ref(null);
const currentOffset = ref(0)
const elWidth = ref(0)
const prettyWidth = ref(0)
const marginAmount = ref(0)
let observer //监听元素大小
let windowSize = 0
let paginationFactor=0
let carouselIndex = 0
let countdownInterval = null //计时器
let moveDirection = -1 //定时动画移动方向
const _LEFT = -1
const _RIGHT = 1
const output=ref('')
const props = defineProps({
listData:{
type:Array,
default: [
{
name: "使命召唤7",
icon: "https://img.543sy.com/images/2023/12/f08598a67de81e1c22a1794b9540bc84.png"
},
{
name: "使命召唤7",
icon: "https://img.543sy.com/images/2023/12/f08598a67de81e1c22a1794b9540bc84.png"
},
{
name: "使命召唤3",
icon: "https://img.543sy.com/images/2023/12/f08598a67de81e1c22a1794b9540bc84.png"
},
{
name: "使命召sdfsf杀杀杀s唤5",
icon: "http://img1.gamedog.cn/2024/01/19/2413736-24011Z952020.jpg"
},
{
name: "使命召唤6",
icon: "http://img1.gamedog.cn/2024/01/19/2413736-24011Z952020.jpg"
},
{
name: "使命召唤9",
icon: "http://img1.gamedog.cn/2024/01/19/2413736-24011Z952020.jpg"
},
{
name: "使命召唤11",
icon: "http://img1.gamedog.cn/2023/12/25/2413736-2312251K5430.jpg"
},
{
name: "使命召唤13",
icon: "https://img.543sy.com/images/2023/11/31afa30137c3f220c17a5401d364472a.png"
},
{
name: "使命召唤14",
icon: "http://oss.lizisy.com/data/upload/game/20231024/65372aed75dca.png"
},
{
name: "使命召唤15",
icon: "http://oss.lizisy.com/upload/operator/20231023/214aa866f723f8c261358c4e1e48a5e3.png"
},
{
name: "使命召唤16",
icon: "http://oss.lizisy.com/upload/operator/20231023/42a079f6d25d1ef79345af75d20bcd5c.png"
}
]
}
})
const atEndOfList = computed(()=> {
return currentOffset.value <= (paginationFactor * -1) * (props.listData.length - windowSize);
})
const atHeadOfList = computed(()=>{
return currentOffset.value === 0;
})
const moveCarousel=(direction)=> {
// Find a more elegant way to express the :style. consider using props to make it truly generic
if (direction === _RIGHT && ! atEndOfList.value) {//向右
carouselIndex--
currentOffset.value -= paginationFactor;
} else if (direction === _LEFT && !atHeadOfList.value) { //向左
carouselIndex++
currentOffset.value += paginationFactor;
}
}
const handleResize = (entries) => {
for (const entry of entries) {
const { width, height } = entry.contentRect;
//console.log(`宽度:${width}px,高度:${height}px`);
// 这里可以执行针对宽高变化的操作
let c = Math.floor(width / 96)
if(c < 10){//剩余空间哪个少选哪个
elWidth.value = 70
}else{
elWidth.value = 80
}
windowSize = Math.floor(width / (elWidth.value + 16))
marginAmount.value = Math.floor(((width - (windowSize * elWidth.value)) / windowSize) / 2) //获取margin的值
paginationFactor = elWidth.value + marginAmount.value * 2
prettyWidth.value = windowSize * (paginationFactor)
currentOffset.value = carouselIndex * paginationFactor//设置新的位移
output.value = "width:" + width + "|c" + c
+"|elWidth.value:" + elWidth.value + "|windowSize:" + windowSize +
"|paginationFactor:"+paginationFactor +"|marginAmount:" + marginAmount.value +
"| prettyWidth.value:" + prettyWidth.value +"|carouselIndex" +carouselIndex
}
}
onMounted(()=>{
// 添加监听
observer = new ResizeObserver(handleResize);
observer.observe(carouselRef.value);
//设置计时器(左右滑动)
countdownInterval = setInterval(() => {
if(atEndOfList.value)
moveDirection=_LEFT
else if(atHeadOfList.value)
moveDirection=_RIGHT
moveCarousel(moveDirection)
}, 4000)
})
onBeforeUnmount(()=>{
//移除添加监听
if(observer){
observer.disconnect();
}
//销毁定时器
if(countdownInterval != null){
clearInterval(countdownInterval);
countdownInterval = null
}
})
</script>
<style scoped>
.card-carousel-out{
display: flex;
justify-content: center;
background-color:red;
/*width:680px;*/
}
.card-carousel {
display: flex;
justify-content: center;
background-color:yellow;
/*width: 580px;/*80*6+((6-1)*20)*/
}
.card-carousel--overflow-container {
overflow: hidden;
}
.card-carousel-cards {
display: flex;
transition: transform 150ms ease-out;
transform: translatex(0px);
}
.card-carousel-cards .card-carousel--card {
display: flex;
flex-direction:column;
align-items:center;
/*margin: 0 10px; /*交给程序计算*/
cursor: pointer;
box-shadow: 0 4px 15px 0 rgba(40, 44, 53, 0.06), 0 2px 2px 0 rgba(40, 44, 53, 0.08);
background-color: #fff;
border-radius: 4px;
z-index: 3;
}
/*.card-carousel-cards .card-carousel--card:first-child {
margin-left: 0;
}
.card-carousel-cards .card-carousel--card:last-child {
margin-right: 0;
}*/
.card-carousel-cards .card-carousel--card img {
vertical-align: bottom;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
transition: opacity 150ms linear;
user-select: none;
}
.card-carousel-cards .card-carousel--card img:hover {
opacity: 0.5;
}
.card-carousel-cards .card-carousel--card div {
text-align: center;
overflow: hidden;
-o-text-overflow: ellipsis;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.875rem;
line-height: 1.25rem;
}
</style>