分析
- 首先我们需要通过css的
clip-path,来裁剪扇形,具体情况下面的方法drawing或者案例代码- 或者通过skew扭曲成扇形
translate(50%,-50%) rotate(${(index * rotate) + (-90+180/lengthNum)}deg) skew(${(360 - (lengthNum * 90))/list.length}deg);transform-origin:0% 100%进行先旋转角度,然后扭曲扇形- 最后通过
transform-origin: center bottom;position: absolute;width: 50%;height:50%;bottom:0;top:revert;transform: translate(-50%, 0) skew(${0 - ((360 - (lengthNum * 90))/list.length)}deg) rotate(${(180 - rotate)/2}deg);,先扭曲负的,然后旋转将里面的内容回正 - 具体情况下面的方法
drawing1或者案例代码
- 通过css动画实现大转盘的旋转
- 通过
animationiteration监听动画重复播放时触发 - 最后获取到了需要旋转的选项,计算出需要旋转的角度,然后停止动画,通过
transition过度动画旋转到选项的角度
-
html:
<div id='app' v-cloak> <div class="whellMain"> <div class="whell" ref="whell" :class="[stop?'whellStop':'']" @animationiteration='animationiteration' @click="zhuanFn"> <div class="whellKuang" v-for="(item,index) in list" :key="index" :ref="`whell_${index}`" :style="{'background-color':item.color}"> <!-- 文字显示的地方 --> <div class="textItem" :ref="`textItem_${index}`">{{item.title}}</div> </div> </div> <div class="btnDian"></div> </div> </div> -
css:
[v-cloak]{ display: none; } .whellMain{ position: relative; width: 400px; height: 400px; } .btnDian{ width: 100px; height: 100px; position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); border-radius: 50%; box-shadow: 5px 5px 10 5px rgba(0,0,0,0.5); background-color: #fff; pointer-events: none; } .btnDian::before{ content: ' '; width: 30px; height: 20px; background-color: #fff; position: absolute; top: 0; left: 50%; transform: translate(-50%,-90%); clip-path:polygon(0 100%,50% 0,100% 100%); -webkit-clip-path:polygon(0 100%,50% 0,100% 100%); } .whell{ width: 100%; height: 100%; border-radius: 50%; border: 1px solid; box-sizing: border-box; position: relative; animation: rotateAm 0.5s infinite linear; transform-origin: center center; } .whellStop{ animation-play-state: paused; } .whellKuang{ width: 100%; height: 100%; border-radius: 50%; position: absolute; top: 0; left: 0; overflow: hidden; } .textItem{ height: 50%; transform-origin: center bottom; position: absolute; top: 0; display: flex; align-items: center; justify-content: center; color: #fff; font-size: 36px; } @keyframes rotateAm{ 0%{ transform:rotate(0deg) } 100%{ transform:rotate(360deg) } }-
js:
- 通过
drawing画扇形
// 方法1(裁剪,计算出扇形,最少两个) drawing(rotate,dom,width,cb){ var mask = dom var r = width/2 var backgroundColor=mask.style['background-color'] || null if(rotate<90){ var rad = rotate * Math.PI/180; var x = r*Math.tan(rad)+r mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,${x}px 0,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,${x}px 0,50% 50%)` }else if(rotate==90){ mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 50%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 50%,50% 50%)` }else if(rotate<180){ var rad1 = (rotate-90) * Math.PI/180; var y = r*Math.tan(rad1)+r mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% ${y}px,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% ${y}px,50% 50%)` }else if(rotate==180){ mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,50% 100%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,50% 100%,50% 50%)` }else if(rotate<270){ var rad2 = (rotate-180) * Math.PI/180; var x1 = r-r*Math.tan(rad2) mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,${x1}px 100%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,${x1}px 100%,50% 50%)` }else if(rotate==270){ mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 50%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 50%,50% 50%)` }else if(rotate<360){ var rad3 = (rotate-270) * Math.PI/180; var y2 = r-r*Math.tan(rad3) mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% ${y2}px,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% ${y2}px,50% 50%)` }else{ mask.style=`background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 0%,50% 0%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 0%,50% 0%)` } cb && cb() }- 通过
animationiteration监听动画,并执行最后的转动
animationiteration(){ if(this.checkObject.du>0){ this.stop=true let time=this.checkObject.du/360*0.5 this.$refs.whell.style.rotate=`${this.checkObject.du}deg` this.$refs.whell.style.transition = `rotate ${time}s ease-out` // 动画停止,传递参数 setTimeout(()=>{ this.endFn(true) },time*1000) } }, // 方法2(使用变形,扭曲成扇形,最少三个) drawing1 (rotate, domObject, index, cb){ //转盘扇形dom 和 内部元素 dom,具体看案例 const {whellDom,textItemDom} = domObject var lengthNum = 360 / rotate const skewDeg = (360 - (lengthNum * 90))/lengthNum const deffAddRotate = (rotate/2)+(-90+180/lengthNum) // 旋转角度,使用skew变形 whellDom.style.transform=`translate(50%,-50%) rotate(${(index * rotate) +deffAddRotate}deg) skew(${skewDeg}deg)` whellDom.style.transformOrigin = '0% 100%' whellDom.style.borderRadius = 'revert' // 文字旋转,并恢复变形 textItemDom.style=`transform-origin: center bottom;position: absolute;width: 50%;height:50%;bottom:0;top:revert;transform: translate(-50%, 0) skew(${0 - skewDeg}deg) rotate(${(180 - rotate)/2}deg);` cb && cb(); }-
案例:
抽离成组件:
使用:<template> <div @click="selectClick"> <turntable class="customizeWrap" ref="turntableDom" @end='whellEnd'> <turntableItem v-for="item in list" :key="item.id"> <template #left> <div style="width: 1px;height: 100%;background-color: #f0c55b;"></div> </template> <div class="textMain"> <div class="textItem"> <div class="title">{{ item.title }}</div> </div> </div> <template #right> <div style="width: 1px;height: 100%;background-color: #f0c55b;"></div> </template> </turntableItem> <template #btn> <div class="">123</div> </template> </turntable> </div> </template> <script setup lang="ts"> interface TurntableInstance { turntableStart: (callback: (stopValue: number) => void) => void; checkSelect: (selectedIndex: number,type?:'start' | 'end' | 'reset') => void; } const turntableDom = ref<TurntableInstance|null>(null); const list = ref<any[]>([ { id: 1, title: "111111" }, { id: 2, title: "222" }, { id: 3, title: "333" }, { id: 4, title: "444" }, { id: 5, title: "555" }, { id: 6, title: "666" } ]); const selectClick=()=>{ //先旋转,后接口 turntableDom.value?.turntableStart((stop:any)=>{ setTimeout(()=>{ // stop(3) turntableDom.value?.checkSelect(2) },3000) }) // 成功的但是指向线(有动画) // turntableDom.value?.checkSelect(-1) // 失败的,突然停止(0:初始位置,-1:线) // turntableDom.value?.checkSelect(-1, 'reset'); } const whellEnd=(index:number)=>{ console.log(index) } </script>turntable<template> <div class="whellMain" :style="{ '--skew':(360 - (childComponents.length * 90))/childComponents.length}"> <transition appear name="fadeWhellBg"> <div class="whellBg" v-if="isPlayRotate"></div> </transition> <div @animationiteration="animationiteration" :style="{ '--amTimeS': amTimeS, '--anEs': optionsData.anEs, transform: `rotate(${optionsData.rotateNum}deg) translate3d(0,0,0)`, transition: optionsData.transitionString }" class="whell" ref="whell" :class="[stop ? 'whellStop' : 'whellPlay']"> <slot></slot> <div class="whellMainSlotBg"> <slot name="bg"></slot> </div> </div> <div class="btnMain"> <slot name="btn"> <div class="btnDian"></div> </slot> </div> </div> </template> <script setup lang="ts"> import { ref,watch, provide, nextTick, onMounted, computed, defineExpose,defineEmits,withDefaults,defineProps } from 'vue' const props = withDefaults(defineProps<{ whellTime?:number // 秒 isCenter?:boolean // 是否居中到第一个 isOffset?:boolean // 是否偏移 }>(), { whellTime:0.8, isCenter:false, isOffset:true }) const emit=defineEmits(['start','end']) const whell = ref<any>(null) const isPlayRotate=ref<boolean>(false) // 管理子组件传递参数 const childComponents = ref<any[]>([]); // 提供注册方法给子组件 provide('parentSetChild', { registerChild: (child: any) => { const index = childComponents.value.findIndex(n=>n.uuid === child.uuid) if(index===-1){ childComponents.value.push(child); } }, removeChild: (child: any) => { const index = childComponents.value.findIndex(n=>n.uuid === child.uuid) if(index!==1){ childComponents.value.splice(index,1) } } }); onMounted(() => { console.log('父组件已挂载,childComponents:', childComponents.value); }); // 方法1(裁剪,计算出扇形,最少两个) const drawing = (rotate: number, dom: HTMLElement, width: number, cb: any) => { var mask = dom; var r = width / 2; var backgroundColor = (mask.style?.backgroundColor as any) || null; if (rotate < 90) { var rad = (rotate * Math.PI) / 180; var x = r * Math.tan(rad) + r; mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,${x}px 0,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,${x}px 0,50% 50%)`; } else if (rotate == 90) { mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 50%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 50%,50% 50%)`; } else if (rotate < 180) { var rad1 = ((rotate - 90) * Math.PI) / 180; var y = r * Math.tan(rad1) + r; mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% ${y}px,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% ${y}px,50% 50%)`; } else if (rotate == 180) { mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,50% 100%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,50% 100%,50% 50%)`; } else if (rotate < 270) { var rad2 = ((rotate - 180) * Math.PI) / 180; var x1 = r - r * Math.tan(rad2); mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,${x1}px 100%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,${x1}px 100%,50% 50%)`; } else if (rotate == 270) { mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 50%,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 50%,50% 50%)`; } else if (rotate < 360) { var rad3 = ((rotate - 270) * Math.PI) / 180; var y2 = r - r * Math.tan(rad3); mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% ${y2}px,50% 50%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% ${y2}px,50% 50%)`; } else { mask.style = `background-color:${backgroundColor};clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 0%,50% 0%);-webkit-clip-path:polygon(50% 50%,50% 0%,100% 0,100% 100%,0% 100%,0% 0%,50% 0%)`; } cb && cb(); } // 方法2(使用变形,扭曲成扇形,最少三个) const drawing1 = (rotate: number, domObject: any, index: number, cb?: any) =>{ const {whellDom,textItemDom} = domObject var lengthNum = 360 / rotate const skewDeg = (360 - (lengthNum * 90))/lengthNum const deffAddRotate = ( props.isCenter?0:rotate/2)+(-90+180/lengthNum) // 旋转角度,使用skew变形(.whellKuang样式) whellDom.style=`position: absolute;top:-50%;left:50%;border-radius:revert;transform-origin:0% 100%;width:100%;height:100%;transform:rotate(${(index * rotate) +deffAddRotate}deg) skew(${skewDeg}deg);` // 文字旋转,并恢复变形(.textItemMain 样式) textItemDom.style=`transform-origin: center bottom;position: absolute;width: 50%;height:50%;bottom:0;top:revert;transform: translate(-50%, 0) skew(${0 - skewDeg}deg) rotate(${(180 - rotate)/2}deg);` cb && cb(); } const whellInit = (objectData: any[]) => { nextTick(() => { const width = whell.value.offsetWidth; const rotate = 360 / objectData.length; const mainWidth = Math.sin(((rotate / 2) * Math.PI) / 180) * (width / 2) * 2; objectData.forEach((item, index) => { let whellDom = item.whellItem let textItemDom = item.textItemMain drawing(rotate, whellDom, width, () => { whellDom.style["transform"] = `rotate(${index * rotate+ (props.isCenter?(-rotate/2):0)}deg)`; textItemDom.style = `width:${mainWidth}px;transform:rotate(${rotate / 2}deg);left:calc(50% - ${mainWidth / 2}px)`; // 设置内容框的位置 // 设置边框角度 const bianRotate = (180 - rotate) / 2; if (item.leftBian) { item.leftBian.style = `position: absolute;bottom: 0;right: 50%;height:100%;transform-origin: 50% 100%;-webkit-transform-origin: 50% 100%;transform: rotate(-${90 - bianRotate}deg);-webkit-transform:rotate(-${90 - bianRotate}deg);z-index:999`; } if (item.rightBian) { item.rightBian.style = `position: absolute;bottom: 0;right: 50%;height:100%;transform-origin: 50% 100%;-webkit-transform-origin: 50% 100%;transform: rotate(${90 - bianRotate}deg);-webkit-transform:rotate(${90 - bianRotate}deg);z-index:999`; } }) // drawing1(rotate, { // whellDom, // textItemDom // },index,()=>{ // textItemDom.style.width=mainWidth+'px' // // 设置边框角度 // const bianRotate = (180 - rotate) / 2; // if (item.leftBian) { // item.leftBian.style = `position: absolute;bottom: 0;right: 50%;height:100%;transform-origin: 50% 100%;-webkit-transform-origin: 50% 100%;transform: rotate(-${90 - bianRotate}deg);-webkit-transform:rotate(-${90 - bianRotate}deg);z-index:999`; // } // if (item.rightBian) { // item.rightBian.style = `position: absolute;bottom: 0;right: 50%;height:100%;transform-origin: 50% 100%;-webkit-transform-origin: 50% 100%;transform: rotate(${90 - bianRotate}deg);-webkit-transform:rotate(${90 - bianRotate}deg);z-index:999`; // } // }) }) }) } const resizeWheelInit = () =>{ whellInit(childComponents.value) } watch(() => childComponents.value, (value) => { if (value.length > 0) { whellInit(value) window.addEventListener('resize', resizeWheelInit,false); } }, { immediate: true, deep: true }) onUnmounted(()=>{ window.removeEventListener('resize', resizeWheelInit,false); }) const isClick = ref<boolean>(false) const stop = ref<boolean>(true) const ORANGE_DATA = { rotateNum: 0, amTime: props.whellTime, anEs: 'linear', isNextDel: true, transitionString: '', checkObject: { index: -1, du: 0, }, }; const optionsData = ref<Record<string, any>>(JSON.parse(JSON.stringify(ORANGE_DATA))) const setTimeOutArray = ref<any>([]) const amTimeS = computed(() => { return optionsData.value.amTime + "s"; }) // 大转盘停止了 const handlerMessage = () => turntableEnd('message'); const handlerNoMessage = () => turntableEnd('noMessage'); const turntableEnd = (endType='message') => { const rotate = 360 / childComponents.value.length; if(endType=='message')emit('end',optionsData.value.checkObject.index) isPlayRotate.value=false isClick.value = false; if(endType=='message'){ let finalDu = optionsData.value.checkObject.du % 360; if(optionsData.value.checkObject.du<0){ finalDu = (props.isCenter? rotate/2:0) + 360 } optionsData.value.checkObject.du = finalDu; optionsData.value.rotateNum = finalDu; optionsData.value.transitionString = `transform 0s `; } whell.value?.removeEventListener("webkitTransitionEnd", handlerMessage, false); whell.value?.removeEventListener("mozTransitionEnd", handlerMessage, false); whell.value?.removeEventListener("MSTransitionEnd", handlerMessage, false); whell.value?.removeEventListener("otransitionend", handlerMessage, false); whell.value?.removeEventListener("transitionend", handlerMessage, false); } // 选择哪一个index const checkSelect = (index: number,type?:'start' | 'end'| 'reset') => { const modeType = type || 'end' const rotate = 360 / childComponents.value.length; const selecthandelr = (selectType='') => { let itemIndex = index; optionsData.value.checkObject.index = index; if (itemIndex != -1) { const deffRotate = rotate/2 optionsData.value.checkObject.du = (360 - (itemIndex * rotate + (props.isCenter?0:rotate / 2))) % 360; let deleteRotate=0 if(deffRotate - 5 >0 && props.isOffset){ const deffRotate2 = Math.random()*(deffRotate - 5 ) deleteRotate = deffRotate2 * (Math.random() < 0.5?-1:1) } optionsData.value.checkObject.du = optionsData.value.checkObject.du - deleteRotate if(selectType==='judeg' && optionsData.value.checkObject.du<180){ optionsData.value.checkObject.du+=360 } }else{ optionsData.value.checkObject.du =-1; } } if(modeType==='start'){ if (isClick.value) return false; emit('start',true) isClick.value = true; isPlayRotate.value=true selecthandelr() optionsData.value.rotateNum = 0; optionsData.value.transitionString = `transform 0s`; setTimeout(()=>{ let turntableRotate = optionsData.value.checkObject.du>0? optionsData.value.checkObject.du:0; optionsData.value.transitionString = `transform ${optionsData.value.amTime*2}s ease-out`; requestAnimationFrame(() => { optionsData.value.rotateNum = turntableRotate+360; whell.value.offsetHeight; }); whell.value.addEventListener("webkitTransitionEnd", handlerMessage, false); whell.value.addEventListener("mozTransitionEnd", handlerMessage, false); whell.value.addEventListener("MSTransitionEnd", handlerMessage, false); whell.value.addEventListener("otransitionend", handlerMessage, false); whell.value.addEventListener("transitionend", handlerMessage, false); }) }else if(modeType==='reset'){ isPlayRotate.value=false isClick.value = false; stop.value = true; optionsData.value = JSON.parse(JSON.stringify(ORANGE_DATA)); Object.assign(optionsData.value.checkObject,{index: -1,du: 0}) optionsData.value.transitionString = `transform 0s `; setTimeOutArray.value.map((item:any)=>{ item && clearTimeout(item) }) setTimeout(()=>{ if(index<0){ optionsData.value.rotateNum = rotate/2; }else{ optionsData.value.rotateNum = 0; } whell.value?.offsetHeight; setTimeOutArray.value=[] handlerNoMessage(); }) }else{ selecthandelr('judeg') } } // 开始旋转 const turntableStart = (cb?:any) => { if (isClick.value) return false; emit('start',true) optionsData.value.amTime = ORANGE_DATA.amTime optionsData.value.anEs = ORANGE_DATA.anEs optionsData.value.isNextDel = ORANGE_DATA.isNextDel isClick.value = true; isPlayRotate.value=true if (optionsData.value.checkObject.du == 0) { //度数为0,直接开始动画 stop.value = true; // 暂停动画 stop.value = false; // 立即重启以应用新时间 } else { // 不为0,需要先转满最后一圈 let zhuanDu = 360 - (optionsData.value.checkObject.du % 360); const time = (zhuanDu / 360) * optionsData.value.amTime; optionsData.value.rotateNum = optionsData.value.checkObject.du + zhuanDu; optionsData.value.transitionString = `transform ${time}s ${optionsData.value.anEs}`; // 再次复制为0 optionsData.value.checkObject.du = 0; optionsData.value.checkObject.index = -1; // 并在旋转结束将whell的度数清零 setTimeOutArray.value.push(setTimeout(() => { // 开始执行动画 stop.value = false; optionsData.value.anEs = "linear"; // 清除过渡角度的 optionsData.value.rotateNum = 0; optionsData.value.transitionString = `transform 0s`; }, time * 1000)) // 直接重置为0,不需要再次旋转 // optionsData.value.checkObject.du = 0; // optionsData.value.checkObject.index = -1; // stop.value = true; // 暂停动画 // stop.value = false; // 立即重启以应用新时间 // requestAnimationFrame(() => { // whell.value.offsetHeight; // }); // setTimeout(()=>{ // optionsData.value.rotateNum = 0; // optionsData.value.transitionString = `transform 0s`; // },0) } if(cb){ cb((index:number)=>{ checkSelect(index) }) } } // 监听动画一个循环结束,发现最后需要旋转的度数大于0,就停止动画,然后通过transition旋转最后的度数 const animationiteration = () => { optionsData.value.anEs = "linear"; if (optionsData.value.checkObject.du != 0) { isPlayRotate.value=false const rotate = 360 / childComponents.value.length; const offsetRotate = (props.isCenter? rotate/2:0) + 360 const turntableRotate = optionsData.value.checkObject.du>0? optionsData.value.checkObject.du:offsetRotate; const time = (turntableRotate<180?turntableRotate+180:turntableRotate)/360 * optionsData.value.amTime * 1.5 optionsData.value.transitionString = `transform ${time}s cubic-bezier(.4,.6,0.8,0.85)`; // 动画不执行了 optionsData.value.amTime=0 requestAnimationFrame(() => { optionsData.value.rotateNum = turntableRotate; whell.value.offsetHeight; }); setTimeOutArray.value.push(setTimeout(() => { stop.value = true; }, optionsData.value.checkObject.du<0?0:time*0.2*1000)) // 最后选中的如果传入-1,表示没有选中,会回到最初位置 if(optionsData.value.checkObject.du<0 && offsetRotate===0){ handlerMessage() }else{ // 过渡结束后执行回调 whell.value.addEventListener("webkitTransitionEnd", handlerMessage, false); whell.value.addEventListener("mozTransitionEnd", handlerMessage, false); whell.value.addEventListener("MSTransitionEnd", handlerMessage, false); whell.value.addEventListener("otransitionend", handlerMessage, false); whell.value.addEventListener("transitionend", handlerMessage, false); } } else { // 越来越慢 if (optionsData.value.amTime >ORANGE_DATA.amTime/4 && optionsData.value.isNextDel) { optionsData.value.amTime -= ORANGE_DATA.amTime * 0.05; } else { // 最多慢两倍 optionsData.value.isNextDel = false; if (optionsData.value.amTime < ORANGE_DATA.amTime * 1.5) { optionsData.value.amTime += ORANGE_DATA.amTime * 0.05; } } } } defineExpose({ turntableStart, checkSelect }) </script> <style lang="scss" scoped> .whellMain { overflow: hidden; position: relative; width: 300px; height: 300px; } .whellMainSlotBg{ position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 50; pointer-events: none; } .btnMain { z-index: 999; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); } .btnDian { width: 50px; height: 50px; border-radius: 50%; box-shadow: 5px 5px 10 5px rgba(0, 0, 0, 0.5); background-color: #fff; pointer-events: none; } .btnDian::before { content: " "; width: 30px; height: 20px; background-color: #fff; position: absolute; top: 8px; left: 50%; transform: translate(-50%, -90%); clip-path: polygon(0 100%, 50% 0, 100% 100%); -webkit-clip-path: polygon(0 100%, 50% 0, 100% 100%); } .whell { width: 100%; height: 100%; border-radius: 50%; box-sizing: border-box; position: relative; z-index: 99; transform: rotate(0deg) translate3d(0,0,0); transform-origin: center center; } .whellPlay { animation: rotateAm var(--amTimeS) infinite var(--anEs); } .whellStop { animation-play-state: paused !important; animation: none !important; } @keyframes rotateAm { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } .whellBg{ width: 100%; height: 100%; position: absolute; border-radius: 50%; top: 0; left: 0; z-index: 998; background: rgba(0,0,0,0.01); // 或背景图片 -webkit-backdrop-filter: blur(3px); backdrop-filter: blur(3px); } /* 进入和离开过渡的公共样式 */ .fadeWhellBg-enter-active, .fadeWhellBg-leave-active { transition: all 0.2s ease; } /* 进入动画 */ .fadeWhellBg-enter-from { opacity: 0; } .fadeWhellBg-enter-to { opacity: 1; } /* 离开动画 */ .fadeWhellBg-leave-from { opacity: 1; } .fadeWhellBg-leave-to { opacity: 0; } </style>turntableItem<template> <div class="whellKuang" ref="whellItem"> <div class="textItemMain" ref="textItemMain"> <!-- 左边框 --> <div class="leftBian" ref="leftBian"> <slot name="left" /> </div> <slot/> <!-- 右边框 --> <div class="rightBian" ref="rightBian"> <slot name="right" /> </div> </div> </div> </template> <script setup lang="ts"> import { ref, onMounted, onUnmounted,inject } from 'vue'; type domType = HTMLElement | Element | null; const whellItem = ref<domType>(null); const textItemMain = ref<domType>(null); const leftBian = ref<domType>(null); const rightBian = ref<domType>(null); interface ParentInterface { registerChild: (child: { whellItem: domType; textItemMain: domType; leftBian: domType; rightBian: domType; uuid: number; }) => void; removeChild: (child: { whellItem: domType; textItemMain: domType; leftBian: domType; rightBian: domType; uuid: number; }) => void; } const parent = inject<ParentInterface>('parentSetChild'); const componentInstance = getCurrentInstance(); let isRegistered = false; const thisChildData = ref<Record<any, any>>({}) onMounted(() => { if (parent && componentInstance && !isRegistered) { const childData = { whellItem: whellItem.value, textItemMain: textItemMain.value, leftBian: leftBian.value, rightBian: rightBian.value, uuid: componentInstance.uid // 使用组件唯一 uid 避免重复注册 }; thisChildData.value =childData if (childData.whellItem && childData.textItemMain) { parent.registerChild(childData); isRegistered = true; } } }); onUnmounted(() => { isRegistered = false; if(Object.keys(thisChildData.value).length && parent && componentInstance ){ parent.removeChild(thisChildData.value as any); } }); </script> <style lang="scss" scoped> .whellKuang { width: 100%; height: 100%; border-radius: 50%; position: absolute; top: 0; left: 0; z-index: 60; overflow: hidden; pointer-events: none; } .textItemMain { height: 50%; pointer-events: all; transform-origin: center bottom; position: absolute; top: 0; display: flex; justify-content: center; overflow: hidden; } </style> - 通过
-