vue3实现卡片左右滚动效果

文章详细展示了如何在游戏下载站点上实现一个动态的游戏图标滚动功能,通过Vue3的模板、计算属性和事件处理函数来管理列表和动画效果。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

看到一些游戏下载站的顶部会有一个游戏图标滚动的效果,以下是一个简易版的实现(基于vu3)。有需要的boy可以作为参考自己再行封装。

原文出处:https://andomahoro.top/#/view/14

效果如下图所示

image

代码如下:

<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>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值