html
复制代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>实现元素沿 圆/椭圆 轨迹运动</title>
<style>
body {
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.circle {
position: relative;
width: 400px;
height: 300px;
border: 1px solid red;
border-radius: 50%;
}
.dot {
position: absolute;
width: 40px;
height: 40px;
text-align: center;
line-height: 40px;
border: 1px solid red;
border-radius: 50%;
}
</style>
</head>
<body>
<div class="circle">
<!-- <div class="dot">点</div> -->
</div>
<script>
/**
* 根据角度和半径计算圆上的点坐标
* @param {number} angle - 角度(0-360度)从正右方开始
* @param {number} radius - 半径
* @param {number} [centerX=0] - 圆心x坐标,默认为0
* @param {number} [centerY=0] - 圆心y坐标,默认为0
* @returns {Object} 包含x和y坐标的对象
*/
function getPointOnCircle(angle, radius, centerX = 0, centerY = 0) {
// angle -= 180 // 从正左方开始
// 将角度转换为弧度(Math.cos和Math.sin使用弧度)
const radians = angle * Math.PI / 180
return {
x: centerX + radius * Math.cos(radians),
y: centerY + radius * Math.sin(radians)
}
}
/**
* 根据角度获取椭圆上的点
* @param {number} a - 椭圆的长半轴(x轴方向)
* @param {number} b - 椭圆的短半轴(y轴方向)
* @param {number} angle - 角度(以度为单位)
* @param {number} [centerX=0] - 椭圆中心的x坐标(可选,默认为0)
* @param {number} [centerY=0] - 椭圆中心的y坐标(可选,默认为0)
* @returns {Object} 包含x和y坐标的对象
*/
function getPointOnEllipse(a, b, angle, centerX = 0, centerY = 0) {
// 将角度转换为弧度
const radians = angle * Math.PI / 180
// 计算椭圆上的点坐标
const x = centerX + a * Math.cos(radians)
const y = centerY + b * Math.sin(radians)
return { x, y }
}
let itemDom = []
let staticDom = []
let data = {}
function toAnimate() {
// 动画之前存储上一个dom数据
let last = itemDom.slice(-1)[0]
if (last) {
data.deg = last.deg
data.startDeg = last.startDeg
data.transform = last.style.transform
data.index = parseInt(last.innerText)
}
// 控制时间 动画频率
// let step = 360 / (6/* 秒 */ * 60)
let step = 0.5
let count = 36
function animate() {
itemDom.forEach(dom => {
let { x, y } = getPointOnEllipse(200, 150, dom.deg, 180, 130)
dom.style.transform = `translate(${x}px,${y}px)`
dom.deg += step
if (dom.deg > 360) {
dom.deg = 0
}
})
count -= step
if (count <= 0) {
let dom = itemDom.shift()
circleDom.removeChild(dom)
staticDom.push(dom)
if ((staticDom.length + itemDom.length) < 12/* 原始数据量,按设计视图最多挂载5个dom */) {
let dotDom = document.createElement('div')
dotDom.classList.add('dot')
dotDom.innerText = parseInt(data.index) + 1
dotDom.deg = data.deg
dotDom.startDeg = data.startDeg
dotDom.style.transform = data.transform
itemDom.push(dotDom)
circleDom.append(dotDom)
} else {
let dotDom = staticDom.shift()
dotDom.deg = data.deg
dotDom.startDeg = data.startDeg
dotDom.style.transform = data.transform
itemDom.push(dotDom)
circleDom.append(dotDom)
}
setTimeout(() => {
toAnimate()
}, 3000 /* 动画间隔时间 */)
} else {
window.requestAnimationFrame(animate)
}
}
window.requestAnimationFrame(animate)
}
let circleDom = document.querySelector('.circle')
function generate(itemCount) {
let fullCount = 5
let start = -215.5
for (let i = 0; i < fullCount; i++) {
let dotDom = document.createElement('div')
dotDom.classList.add('dot')
dotDom.innerText = i
let { x, y } = getPointOnEllipse(200, 150, start, 180, 130)
dotDom.deg = start
dotDom.startDeg = start
dotDom.style.transform = `translate(${x}px,${y}px)`
itemDom.push(dotDom)
circleDom.append(dotDom)
start -= 36
if (start < -360) {
start = start - -360
}
}
}
generate(12)
// 当量小于等于4个时则不进行动画
toAnimate()
</script>
</body>
</html>