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