Nuxt.js 2.x动态适配多扇区的甜甜圈组件实现咨询
Nuxt.js 2.x动态适配多扇区的甜甜圈组件实现咨询
嗨,我来帮你搞定这个动态甜甜圈的需求!咱们得把原来固定3扇区的逻辑改成支持3-10个动态大扇区,每个大扇区仍保留4个子扇区,同时还得维持原来的间隙效果。一步步来改造:
1. 调整组件Props,新增动态扇区配置
首先给组件添加一个控制大扇区数量的prop,同时计算总子扇区数:
<script> import { msToLapTime } from '@/utils' export default { props: { title: { type: String, default: 'Lap', }, value: { type: Number, default: 0, }, currentSector: { type: Number, default: 0, }, sectors: { type: Object, default: () => {}, }, // 新增:动态大扇区数量(限制3-10) sectorCount: { type: Number, default: 3, validator: val => val >=3 && val <=10 }, }, computed: { speedValue() { return this.value > 0 ? msToLapTime(this.value, this.$dateFns).slice(0, 7) : '00:00.0' }, // 计算总子扇区数:大扇区数 * 4 totalSegments() { return this.sectorCount * 4 } }, // ... 其他代码 } </script>
2. 修改模板,动态渲染子扇区
把原来固定的count替换成计算属性totalSegments,同时给每个子扇区绑定动态样式:
<template> <div class="lap is-flex is-flex-direction-column"> <div class="live-label is-flex is-justify-content-center" v-text="title" /> <div class="lap-content"> <div class="circle"> <ul class="circle__wrapper"> <li v-for="index in totalSegments" :key="index" class="circle__section" :style="getSegmentStyle(index)" /> </ul> <div class="circle__center is-flex is-align-items-center is-justify-content-center"> <div class="live-label--large" v-text="speedValue" /> </div> </div> </div> </div> </template>
3. 新增动态样式计算方法,替代固定Sass Mixin
原来的Sass mixin是编译时固定12份,现在咱们用JS计算每个子扇区的旋转、倾斜角度,实现动态布局,同时保留间隙:
<script> // ... 其他代码 export default { // ... props、computed methods: { // 计算每个子扇区的动态样式 getSegmentStyle(index) { const total = this.totalSegments // 单个子扇区的角度:360 / 总份数 const segmentAngle = 360 / total // 旋转角度:(索引-1)*单个扇区角度 const rotateAngle = (index - 1) * segmentAngle // 倾斜角度:和原来mixin逻辑一致,保持扇形形状 const skewAngle = -360 / (total / 2) // 间隙调整:数值越小间隙越窄,可根据需求修改 return { transform: `rotate(${rotateAngle}deg) skewY(${skewAngle}deg) translate(3px, -3px)` } }, // ... 其他方法 } } </script>
同时修改Sass样式,去掉固定的circle-fraction mixin调用,调整基础样式:
$grid-size: #{'(100vh - 185px) / 8'} @mixin grid-elem-size($property, $size){ #{$property}: calc(#{$grid-size} * #{$size})!important; } .circle { position: relative; &__wrapper { border-radius: 50%; position: relative; padding: 0; margin: 1em auto; list-style: none; overflow: hidden; &::before { content: ''; position: absolute; z-index: 2; width: calc(100% + 2px); height: calc(100% + 2px); top: -1px; left: -1px; border-radius: 50%; mask-image: radial-gradient(farthest-side, transparent calc(100% - 2px), #fff 100%); transform: rotate(1deg); } @include grid-elem-size('height', 2.5); @include grid-elem-size('width', 2.5); } &__section { position: absolute; top: 0; right: 0; width: 50%; height: 50%; background-color: #656565; transform-origin: 0% 100%; // 去掉原来固定的mixin调用,改用动态样式绑定 &.filled { background-color: #d0cfcf; animation-name: color; animation-duration: 2s; animation-iteration-count: infinite; } } &__center { position: absolute; top: calc(50% - #{$grid-size} * 1.1); left: calc(50% - #{$grid-size} * 1.1); border-radius: 50%; background-color: var(--background-color); @include grid-elem-size('height', 2.2); @include grid-elem-size('width', 2.2); &::before { content: ''; position: absolute; width: 100%; height: 100%; border-radius: 50%; mask-image: radial-gradient(farthest-side, transparent calc(100% - 1px), #fff 100%); transform: rotate(1.5deg); } } } // 保留原来的闪烁动画 @keyframes color { 0% { background-color: #d0cfcf; } 50% { background-color: #e8e8e8; } 100% { background-color: #d0cfcf; } }
4. 重写setSegmentColour方法,适配动态扇区
原来的固定分界(4、8)要改成根据大扇区数动态计算每个大扇区对应的子扇区范围:
<script> export default { // ... 其他代码 methods: { // ... getSegmentStyle方法 setSegmentColour() { const sectionArr = document.querySelectorAll('.circle__section') const segmentsPerSector = 4 // 每个大扇区固定4个子扇区 // 先重置所有子扇区的样式 sectionArr.forEach(el => { el.className = 'circle__section' }) // 给已完成的大扇区设置对应颜色,当前扇区设置filled for (let sector = 1; sector <= this.currentSector; sector++) { const startIndex = (sector - 1) * segmentsPerSector const endIndex = sector * segmentsPerSector for (let i = startIndex; i < endIndex; i++) { if (sector < this.currentSector) { // 已完成的扇区,取对应颜色 const colorClass = this.sectors[`s${sector}Colour`] if (colorClass) { sectionArr[i].classList.add(colorClass) } } else { // 当前扇区,设置filled样式 sectionArr[i].classList.add('filled') } } } }, }, watch: { currentSector() { this.setSegmentColour() }, sectorCount() { // 扇区数变化时,DOM更新后重新渲染样式 this.$nextTick(() => { this.setSegmentColour() }) } }, mounted() { this.setSegmentColour() }, } </script>
额外提示
- 间隙大小可以通过
getSegmentStyle里的translate(3px, -3px)调整,数值越小间隙越窄 - 如果需要更灵活的样式控制,可以把间隙大小做成prop传入
- 测试时可以给
sectorCount传不同的值(比如5、7),验证甜甜圈的布局是否正确
备注:内容来源于stack exchange,提问作者Nina Herasymova
相关产品推荐
相关产品推荐

