获取滚动容器内垂直居中子元素的值

241 阅读1分钟

最近要实现一个滚动列表抽奖的demo,卡在获取居中元素那里,后来询问同事后解决,这里仅此记录:

image.png

<div class="main">
  <div ref="roll" class="lotteryBox">
    <div v-for="item in listData" :key="item.id" class="lotteryItem" :data-id="item.id">
      <slot v-if="$slots.customLottery"></slot>
      <span v-else>{{item.name}}</span>
    </div>
  </div>
</div>
 // 计算子元素中心值和容器中心差值绝对值
  function calculatesNum(element) {
    const { top: mainTop, bottom: mainBottom } = roll.value.getBoundingClientRect();
    const mainCenter = (mainTop + mainBottom) / 2; // 容器中心
    const { top, bottom } = element.getBoundingClientRect(); 
    const elCenter = (top + bottom) / 2; // 子元素中心
    return Math.abs(mainCenter - elCenter);
  };
  
  // 获取居中元素并设置状态
  function setLotteryActive() {
    const list = document.querySelectorAll('.lotteryItem'); // 所有子元素
    const numsArr = [];
    for(let i = 0; i < list.length; i++) {
      const num = calculatesNum(list[i]);
      numsArr.push({
        num,
        el: list[i]
      });
    }
    const resMin = Math.min.apply(null, numsArr.map((k) => k.num));
    const existEl = numsArr.find((k) => k.num === resMin);
    if (existEl) {
      existEl.el.className = 'lotteryItem lotteryActive';
      const id = existEl.el.getAttribute('data-id');
      tempDelId.value = id;
    }
  };
  • 完整代码如下
<template>
  <div>
    <div class="main">
      <div ref="roll" class="lotteryBox">
        <div v-for="item in listData" 
            :key="item.id" 
            class="lotteryItem" 
            :data-id="item.id">
          <slot v-if="$slots.customLottery"></slot>
          <span v-else>{{item.name}}</span>
        </div>
      </div>
    </div>
  </div>
  <el-button @click="lotteryStart">开始</el-button>
  <el-button @click="lotteryEnd">暂停</el-button>
</template>


<script setup lang="ts" name="Lottery">
    import { ref, reactive, onBeforeUnmount, onUnmounted, watch, onMounted, nextTick } from 'vue';
    import type { AllUserRes } from '@/api/login/types';
    const props = defineProps({
        list: {
            type: Array,
            default: () => ([])
        },
        disabled: {
            type: Boolean,
            default: false,
        }
    });

    const emit = defineEmits(['start', 'stop']);

    //定时器初始化
    let timer = ref();
    let speedTimer = ref();
    //ref绑定初始化
    let roll = ref<HTMLElement | null>(null);
    //列表数据初始化
    const listData = ref<AllUserRes[]>([])

    watch(() => props.list as AllUserRes[], async (val: AllUserRes[]) => {
        if (val && val.length) {
            listData.value = val;
        }
        // await nextTick()
        // start('init');
    }, {
        immediate: true,
    })

    // 初始化默认滚动 stepNum 为4
    onMounted( async() => {
        await nextTick();
        start('init');
    })

    //开启定时器方法
    let stepNum = ref(4);

    // 设置top加速度
    function setSpeed() {
        speedTimer.value = requestAnimationFrame(setSpeed)
        const maxSpeed = 50;
        if (stepNum.value === maxSpeed) {
            stepNum.value = maxSpeed;
            cancelAnimationFrame(speedTimer.value);
            return
        }
        stepNum.value += 1;
    }

    const MarqueeTest = () => {
        timer.value = requestAnimationFrame(MarqueeTest)
        let rollEl = roll.value;
        if (!rollEl) {
            return;
        }

        //判断组件是否渲染完成
        if (rollEl.offsetHeight == 0) {
            rollEl = roll.value;
        } else {
            //如果列表数量过少不进行滚动
            if (rollEl.childNodes.length < 6) {
                cancelAnimationFrame(timer.value);
                return;
            }
            rollEl.scrollTop += stepNum.value;
            //判断滚动条是否滚动到底部
            if (Math.round(rollEl.scrollTop) == rollEl.scrollHeight - rollEl.clientHeight) {
                //获取组件第一个节点
                // let a = rollEl.childNodes[0];
                // //删除节点
                // rollEl.removeChild(a);
                // //将该节点拼接到组件最后
                // rollEl.append(a);

                const firstEl = listData.value[0];
                listData.value.splice(0 ,1);
                listData.value.push(firstEl);
            }
        }
    };

    // 计算子元素中心值和容器中心差值绝对值
    const calculatesNum = (element: HTMLElement) => {
        if (!roll.value) {
            return;
        }
        const { top: mainTop, bottom: mainBottom } = roll.value.getBoundingClientRect();
        const mainCenter = (mainTop + mainBottom) / 2; // 容器中心
        const { top, bottom } = element.getBoundingClientRect();
        const elCenter = (top + bottom) / 2; // 子元素中心
        // console.log('elCenter--->', top, bottom, elCenter)
        return Math.abs(mainCenter - elCenter);
    };

    // 获取居中元素并设置状态
    const setLotteryActive = () => {
        if (!roll.value) {
            return;
        }
        const list = roll.value.children;
        const numsArr: any[] = [];
        for (let i = 0; i < list.length; i++) {
            const num = calculatesNum(list[i] as HTMLElement);
            numsArr.push({
                num,
                el: list[i]
            });
        }
        const resMin = Math.min.apply(null, numsArr.map((k) => k.num));
        const existEl = numsArr.find((k) => k.num === resMin);
        if (existEl) {
            existEl.el.className = 'lotteryItem lotteryActive';
            const id = existEl.el.getAttribute('data-id');
            const name = existEl.el.getAttribute('data-name');
            tempLotteryConfig.value = {
                id,
                name
            };
        }
    };
    // 开始loading
    const startBtnLoading = ref<boolean>(false);
    // 获奖id
    const tempLotteryConfig = ref()

    function start(initEnyty?: string) {

        !initEnyty && setSpeed()
        //清除定时器
        timer.value && cancelAnimationFrame(timer.value);
        //组件进行滚动
        MarqueeTest()
    }
    // 暂停
    const lotteryEnd = () => {
        stepNum.value = 4;
        cancelAnimationFrame(timer.value);
        cancelAnimationFrame(speedTimer.value);
        setLotteryActive();
        emit('stop', tempLotteryConfig.value, () => {
            startBtnLoading.value = false;
        });
        startBtnLoading.value = false;
    }

    // 开始
    const lotteryStart = () => {
        if (startBtnLoading.value || props.disabled) {
            return
        }
        startBtnLoading.value = true;
        start()
        const list = document.querySelectorAll('.lotteryActive')
        for (let i = 0; i < list.length; i++) {
            list[i].className = 'lotteryItem';
        }
        emit('start');
    }

    onBeforeUnmount(() => {
        console.log('====onBeforeUnmount====')
        timer.value && cancelAnimationFrame(timer.value);
        speedTimer.value && cancelAnimationFrame(speedTimer.value);
    })
    onUnmounted(() => {
        console.log('====onUnmounted====')
        timer.value && cancelAnimationFrame(timer.value);
        speedTimer.value && cancelAnimationFrame(speedTimer.value);
    })
</script>
<style lang="scss">
.lotteryActive {
    position: relative;
    &:after {
        content: '';
        position: absolute;
        top: -10px;
        width: calc(100% + 10vw);;
        height: 0.15vw;
        left: -5vw;
        background: rgb(232, 165, 22);
    }
    &:before {
        content: '';
        position: absolute;
        bottom: -10px;
        width: calc(100% + 10vw);;
        height: 0.15vw;
        left: -5vw;
        background: rgb(232, 165, 22);
    }
}
</style>
<!-- 列表抽奖 -->
<template>
  <div>
   <Lottery :list="listData" @stop="onLotteryStop"></Lottery>
  </div>
</template>

<script setup>
  import Lottery from '@/components/Lottery.vue'
  import { reactive } from 'vue'
  //列表数据初始化
  let listData = reactive([
    {name:'我是dom第一个', id: 'a1'},
    {name:'我是dom第二个', id: 'a2'},
    {name:'我是dom第三个', id: 'a3'},
    {name:'我是dom第四个', id: 'a4'},
    {name:'我是dom第五个', id: 'a5'},
    {name:'我是dom第六个', id: 'a6'},
    {name:'我是dom第七个', id: 'a7'},
    {name:'我是dom第八个', id: 'a8'},
    {name:'我是dom第九个', id: 'a9'},
    {name:'我是dom第十个', id: 'a10'},
    {name:'我是dom第11个', id: 'a11'},
    {name:'我是dom第12个', id: 'a12'},
    {name:'我是dom第13个', id: 'a13'},
    {name:'我是dom第14个', id: 'a14'},
    {name:'我是dom第15个', id: 'a15'},
    {name:'我是dom第16个', id: 'a16'},
    {name:'我是dom第17个', id: 'a17'},
    {name:'我是dom第18个', id: 'a18'},
    {name:'我是dom第19个', id: 'a19'},
  ]);

  const onLotteryStop = (id) => {
    console.error('onLotteryStop--->', id);
  }
	
</script>