131 lines
3.0 KiB
Vue
131 lines
3.0 KiB
Vue
<template>
|
|
<view id="arc-wrapper" @touchstart="onStart" @touchmove.prevent="onMove" @touchend="onEnd">
|
|
<!-- 父容器旋转 -->
|
|
<view id="arc" :style="{ transform: `rotate(${rotation}deg)` }">
|
|
<view v-for="(item, idx) in items" :key="idx" class="arc-item" :style="getItemStyle(idx)">
|
|
<!-- 文字反向旋转,保持水平 -->
|
|
<view class="item-content" :style="{
|
|
transform: `rotate(${-rotation}deg)`,
|
|
transformOrigin: 'center center'
|
|
}">
|
|
<text>{{ item }}</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
<script setup>
|
|
import {
|
|
ref,
|
|
onMounted,
|
|
getCurrentInstance
|
|
} from 'vue';
|
|
|
|
const items = ['A', 'B', 'C', 'D', 'E'];
|
|
const radius = 100;
|
|
const rotation = ref(0);
|
|
const startAngle = ref(0);
|
|
const centerLocal = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
const centerScreen = {
|
|
x: 0,
|
|
y: 0
|
|
};
|
|
|
|
const instance = getCurrentInstance();
|
|
|
|
// 获取半圆容器在屏幕中的中心坐标
|
|
onMounted(() => {
|
|
uni.createSelectorQuery()
|
|
.in(instance)
|
|
.select('#arc')
|
|
.boundingClientRect(rect => {
|
|
centerLocal.x = rect.width / 2;
|
|
centerLocal.y = rect.height;
|
|
centerScreen.x = rect.left + centerLocal.x;
|
|
centerScreen.y = rect.top + centerLocal.y;
|
|
})
|
|
.exec();
|
|
});
|
|
|
|
// 计算触摸点相对于屏幕圆心的角度
|
|
function calcAngle(x, y) {
|
|
const dx = x - centerScreen.x;
|
|
const dy = y - centerScreen.y;
|
|
return Math.atan2(dy, dx) * 180 / Math.PI; // rotate() 基于度数 :contentReference[oaicite:6]{index=6}
|
|
}
|
|
|
|
function onStart(e) {
|
|
const {
|
|
clientX,
|
|
clientY
|
|
} = e.touches[0];
|
|
startAngle.value = calcAngle(clientX, clientY);
|
|
}
|
|
|
|
function onMove(e) {
|
|
const {
|
|
clientX,
|
|
clientY
|
|
} = e.touches[0];
|
|
const current = calcAngle(clientX, clientY);
|
|
rotation.value += current - startAngle.value;
|
|
startAngle.value = current;
|
|
}
|
|
|
|
function onEnd() {
|
|
// 可加惯性或回正逻辑
|
|
}
|
|
|
|
// 计算每个子项在半圆上的定位
|
|
function getItemStyle(idx) {
|
|
const per = 180 / (items.length + 1);
|
|
const baseAngle = -90 + per * (idx + 1);
|
|
const rad = baseAngle * Math.PI / 180;
|
|
const x = centerLocal.x + radius * Math.cos(rad);
|
|
const y = centerLocal.y + radius * Math.sin(rad);
|
|
return {
|
|
position: 'absolute',
|
|
left: `${x}px`,
|
|
top: `${y}px`,
|
|
transform: 'translate(-50%,-50%)'
|
|
};
|
|
}
|
|
</script>
|
|
<style lang="less" scoped>
|
|
#arc-wrapper {
|
|
width: 200px;
|
|
height: 100px;
|
|
position: absolute;
|
|
bottom: 300rpx;
|
|
right: 100rpx;
|
|
overflow: hidden;
|
|
z-index: 100;
|
|
}
|
|
|
|
#arc {
|
|
width: 200px;
|
|
height: 100px;
|
|
position: absolute;
|
|
bottom: 0;
|
|
left: 0;
|
|
transform-origin: center bottom;
|
|
/* 半圆底部中心为旋转中心 :contentReference[oaicite:7]{index=7} */
|
|
}
|
|
|
|
.arc-item {
|
|
/* 可自定义尺寸 */
|
|
}
|
|
|
|
.item-content {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
/* 确保 transform-origin 在自身中心 */
|
|
transform-box: fill-box;
|
|
/* 在某些渲染环境需指定 reference box :contentReference[oaicite:8]{index=8} */
|
|
transform-origin: center center;
|
|
}
|
|
</style> |