如何写一个转盘
需求描述
- 实现一个圆形转盘组件,转盘上的元素均匀分布,同时保持转盘元素的水平轴始终指向圆心。保证转盘最左边的元素是水平放置,正常展示。
- 点击地下一排按钮,则转盘上对应的元素转到最左边。
实现
思路
核心实现机制:
- 外部的容器,也就是圆周,采用相对定位
- 每个数字块的定位采用绝对定位 + transform,旋转好角度后有序放在圆周上
交互逻辑:
- 点击底部按钮时,计算目标数字所需旋转角度
- 转盘通过 CSS transform: rotate() 实现整体旋转
- 通过 CSS transition 实现平滑旋转动画
页面结构
什么是静态的?
- 转盘的形状
- 转盘元素的形状
- 转盘元素相对于圆盘水平线的角度
什么是动态的?
- 圆盘的旋转角度
什么是要算的?
- 圆盘的旋转角度
- 转盘元素相对于圆盘水平线的角度
动态的和要算的要写成变量或者方法
实现机制
不要去想转了多少度,不要想成计算;而是应该每个位置对应一个唯一的旋转角度,要想成映射
计算转盘元素相对于圆盘水平线的角度,包含三个关键步骤:
- rotate(角度) - 按序分布在圆周上
- translate(-150px) - 将数字移到圆周上,这里应为要保证最左边的元素是我们要展示的元素,所以是负数
translate(-150px) translate(-50%)
确保圆盘元素的中心在圆周的上
// 计算每个数字块的位置和角
getItemStyle(index) {
const anglePerSector = 360 / this.totalSectors;
const rotateAngle = index * anglePerSector;
return {
transform: `rotate(${rotateAngle}deg) translate(-150px) translate(-50%) translateY(-50%)`,
};
},
旋转转盘的方法,也就是要算出目标位置对应的角度是多少
// 计算按钮点击后转盘的旋转角度
rotateToNumber(index) {
const anglePerSector = 360 / this.totalSectors;
const targetAngle = index * anglePerSector;
// 为了让目标数字转到最左边,我们需要相应调整角度
this.rotation = -targetAngle;
},
组件代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script src="./js/vue.min.js"></script>
<title>Title</title>
</head>
<body>
<div id="vue_root">
<div class="wheel-container">
<!-- 转盘 -->
<div class="wheel" :style="{ transform: `rotate(${rotation}deg)` }">
<div v-for="(item, index) in numbers" :key="index" class="wheel-item" :style="getItemStyle(index)">
{{ item }}
</div>
</div>
<!-- 数字按钮 -->
<div class="buttons">
<button v-for="(item, index) in numbers" :key="index" @click="rotateToNumber(index)">
{{ item }}
</button>
</div>
</div>
</div>
</body>
<script>
let vm = new Vue({
el: "#vue_root",
data() {
return {
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9], // 转盘上的数字
rotation: 0, // 转盘的旋转角度
totalSectors: 9, // 转盘分为9个部分
};
},
methods: {
// 计算按钮点击后转盘的旋转角度
rotateToNumber(index) {
const anglePerSector = 360 / this.totalSectors;
const targetAngle = index * anglePerSector;
// 为了让目标数字转到最左边,我们需要相应调整角度
this.rotation = -targetAngle;
},
// 计算每个数字块的位置和角
getItemStyle(index) {
const anglePerSector = 360 / this.totalSectors;
const rotateAngle = index * anglePerSector;
return {
transform: `rotate(${rotateAngle}deg) translate(-150px) translate(-50%) translateY(-50%)`,
};
},
},
})
</script>
<style>
.wheel-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-top: 50px;
}
.wheel {
width: 300px;
height: 300px;
border-radius: 50%;
border: 2px solid #000;
position: relative;
/*left: 50%;*/
margin-bottom: 20px;
transition: transform 1s ease-in-out; /* 转盘旋转时的动画效果 */
}
.wheel-item {
position: absolute;
top: 50%;
left: 50%;
transform-origin: 0 0; /* 以左上角为原点进行旋转 */
width: 30px;
height: 30px;
background-color: #f1c40f;
text-align: center;
line-height: 30px;
border-radius: 50%;
}
.buttons {
display: flex;
justify-content: center;
}
button {
margin: 0 5px;
padding: 10px 20px;
background-color: #3498db;
color: white;
border: none;
cursor: pointer;
}
button:hover {
background-color: #2980b9;
}
</style>
</html>