<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>光标绕屏旋转 · 纯CSS3</title>
<style>
*,
*::before,
*::after {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--pad: 32px;
--dur: 6s;
--size: 16px;
}
body {
width: 100vw;
height: 100vh;
background: #05050f;
overflow: hidden;
cursor: none;
display: flex;
align-items: center;
justify-content: center;
}
.stars {
position: fixed;
inset: 0;
pointer-events: none;
}
.stars::before,
.stars::after {
content: "";
position: absolute;
inset: 0;
background-image:
radial-gradient(
1px 1px at 10% 15%,
rgba(255, 255, 255, 0.6) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 25% 60%,
rgba(200, 180, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1.5px 1.5px at 40% 30%,
rgba(255, 255, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 55% 80%,
rgba(180, 200, 255, 0.6) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 70% 20%,
rgba(255, 255, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1.5px 1.5px at 80% 55%,
rgba(200, 180, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 90% 75%,
rgba(255, 255, 255, 0.6) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 15% 85%,
rgba(180, 220, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 35% 45%,
rgba(255, 255, 255, 0.3) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 65% 10%,
rgba(200, 180, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1.5px 1.5px at 50% 50%,
rgba(255, 255, 255, 0.3) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 5% 40%,
rgba(180, 200, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 95% 35%,
rgba(255, 255, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 45% 95%,
rgba(200, 180, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 75% 90%,
rgba(180, 220, 255, 0.5) 0%,
transparent 100%
);
animation: twinkle 4s ease-in-out infinite alternate;
}
.stars::after {
background-image:
radial-gradient(
1px 1px at 20% 25%,
rgba(255, 255, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 60% 40%,
rgba(200, 180, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1.5px 1.5px at 85% 15%,
rgba(255, 255, 255, 0.6) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 30% 70%,
rgba(180, 200, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 72% 65%,
rgba(255, 255, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 8% 55%,
rgba(200, 180, 255, 0.4) 0%,
transparent 100%
),
radial-gradient(
1px 1px at 48% 8%,
rgba(180, 220, 255, 0.5) 0%,
transparent 100%
),
radial-gradient(
1.5px 1.5px at 92% 88%,
rgba(255, 255, 255, 0.4) 0%,
transparent 100%
);
animation-delay: 2s;
animation-duration: 5s;
}
@keyframes twinkle {
0% {
opacity: 0.6;
}
100% {
opacity: 1;
}
}
.track {
position: fixed;
inset: var(--pad);
border-radius: 20px;
border: 1px dashed rgba(167, 139, 250, 0.15);
box-shadow:
0 0 40px rgba(167, 139, 250, 0.04) inset,
0 0 40px rgba(167, 139, 250, 0.04);
pointer-events: none;
}
.center {
text-align: center;
user-select: none;
pointer-events: none;
position: relative;
z-index: 1;
}
.center h1 {
font-family: "Segoe UI", system-ui, sans-serif;
font-size: clamp(2.5rem, 6vw, 5rem);
font-weight: 800;
letter-spacing: 0.12em;
background: linear-gradient(135deg, #c084fc, #818cf8, #34d399);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
animation: title-pulse 3s ease-in-out infinite alternate;
}
.center p {
margin-top: 0.6rem;
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 0.85rem;
letter-spacing: 0.35em;
text-transform: uppercase;
color: rgba(255, 255, 255, 0.2);
}
@keyframes title-pulse {
from {
filter: brightness(1);
}
to {
filter: brightness(1.3);
}
}
.cursor-wrap {
position: fixed;
top: 0;
left: 0;
width: 0;
height: 0;
pointer-events: none;
z-index: 100;
}
.cursor {
position: fixed;
width: var(--size);
height: var(--size);
border-radius: 50%;
pointer-events: none;
offset-rotate: auto;
transform-origin: center center;
animation: orbit var(--dur) linear infinite;
}
.cursor {
offset-path: none;
top: var(--pad);
left: var(--pad);
background: radial-gradient(circle at 35% 35%, #e0c8ff, #818cf8);
box-shadow:
0 0 12px 4px rgba(192, 132, 252, 0.8),
0 0 30px 10px rgba(129, 140, 248, 0.4);
animation:
orbit-pos var(--dur) linear infinite,
cursor-glow var(--dur) linear infinite,
cursor-scale 1.5s ease-in-out infinite alternate;
}
@keyframes orbit-pos {
0% {
transform: translate(0px, 0px) rotate(0deg) scaleX(1.6);
}
24.9% {
transform: translate(calc(100vw - 2 * var(--pad) - var(--size)), 0px)
rotate(0deg) scaleX(1.6);
}
25% {
transform: translate(calc(100vw - 2 * var(--pad) - var(--size)), 0px)
rotate(90deg) scaleX(1.6);
}
49.9% {
transform: translate(
calc(100vw - 2 * var(--pad) - var(--size)),
calc(100vh - 2 * var(--pad) - var(--size))
)
rotate(90deg) scaleX(1.6);
}
50% {
transform: translate(
calc(100vw - 2 * var(--pad) - var(--size)),
calc(100vh - 2 * var(--pad) - var(--size))
)
rotate(180deg) scaleX(1.6);
}
74.9% {
transform: translate(0px, calc(100vh - 2 * var(--pad) - var(--size)))
rotate(180deg) scaleX(1.6);
}
75% {
transform: translate(0px, calc(100vh - 2 * var(--pad) - var(--size)))
rotate(270deg) scaleX(1.6);
}
100% {
transform: translate(0px, 0px) rotate(360deg) scaleX(1.6);
}
}
@keyframes cursor-glow {
0% {
background: radial-gradient(circle at 35% 35%, #f0e0ff, #a78bfa);
box-shadow:
0 0 14px 5px rgba(167, 139, 250, 0.9),
0 0 35px 12px rgba(139, 92, 246, 0.4);
}
25% {
background: radial-gradient(circle at 35% 35%, #bfdbfe, #60a5fa);
box-shadow:
0 0 14px 5px rgba(96, 165, 250, 0.9),
0 0 35px 12px rgba(59, 130, 246, 0.4);
}
50% {
background: radial-gradient(circle at 35% 35%, #a7f3d0, #34d399);
box-shadow:
0 0 14px 5px rgba(52, 211, 153, 0.9),
0 0 35px 12px rgba(16, 185, 129, 0.4);
}
75% {
background: radial-gradient(circle at 35% 35%, #fde68a, #f59e0b);
box-shadow:
0 0 14px 5px rgba(245, 158, 11, 0.9),
0 0 35px 12px rgba(217, 119, 6, 0.4);
}
100% {
background: radial-gradient(circle at 35% 35%, #f0e0ff, #a78bfa);
box-shadow:
0 0 14px 5px rgba(167, 139, 250, 0.9),
0 0 35px 12px rgba(139, 92, 246, 0.4);
}
}
@keyframes cursor-scale {
from {
transform-origin: center;
}
}
.trail {
position: fixed;
top: var(--pad);
left: var(--pad);
border-radius: 50%;
pointer-events: none;
z-index: 99;
}
.trail-1 {
width: 14px;
height: 14px;
opacity: 0.55;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-1 * var(--dur) / 60);
background: rgba(167, 139, 250, 0.6);
box-shadow: 0 0 10px 3px rgba(167, 139, 250, 0.4);
}
.trail-2 {
width: 12px;
height: 12px;
opacity: 0.45;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-2 * var(--dur) / 60);
background: rgba(139, 114, 240, 0.5);
box-shadow: 0 0 8px 3px rgba(139, 114, 240, 0.3);
}
.trail-3 {
width: 10px;
height: 10px;
opacity: 0.35;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-3 * var(--dur) / 60);
background: rgba(120, 100, 220, 0.4);
}
.trail-4 {
width: 9px;
height: 9px;
opacity: 0.27;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-4 * var(--dur) / 60);
background: rgba(100, 130, 240, 0.4);
}
.trail-5 {
width: 7px;
height: 7px;
opacity: 0.2;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-5 * var(--dur) / 60);
background: rgba(80, 160, 220, 0.35);
}
.trail-6 {
width: 6px;
height: 6px;
opacity: 0.14;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-7 * var(--dur) / 60);
background: rgba(60, 180, 200, 0.3);
}
.trail-7 {
width: 4px;
height: 4px;
opacity: 0.1;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-9 * var(--dur) / 60);
background: rgba(50, 200, 180, 0.25);
}
.trail-8 {
width: 3px;
height: 3px;
opacity: 0.07;
animation: orbit-pos var(--dur) linear infinite;
animation-delay: calc(-12 * var(--dur) / 60);
background: rgba(40, 210, 160, 0.2);
}
.corner-glow {
position: fixed;
width: 120px;
height: 120px;
border-radius: 50%;
pointer-events: none;
z-index: 0;
filter: blur(40px);
animation: corner-pulse 3s ease-in-out infinite alternate;
}
.corner-glow.tl {
top: 0;
left: 0;
background: rgba(167, 139, 250, 0.12);
animation-delay: 0s;
}
.corner-glow.tr {
top: 0;
right: 0;
background: rgba(96, 165, 250, 0.1);
animation-delay: 0.75s;
}
.corner-glow.br {
bottom: 0;
right: 0;
background: rgba(52, 211, 153, 0.1);
animation-delay: 1.5s;
}
.corner-glow.bl {
bottom: 0;
left: 0;
background: rgba(245, 158, 11, 0.1);
animation-delay: 2.25s;
}
@keyframes corner-pulse {
from {
opacity: 0.4;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1.2);
}
}
.hint {
position: fixed;
bottom: 18px;
left: 50%;
transform: translateX(-50%);
color: rgba(255, 255, 255, 0.12);
font-family: "Segoe UI", system-ui, sans-serif;
font-size: 0.65rem;
letter-spacing: 0.2em;
text-transform: uppercase;
pointer-events: none;
white-space: nowrap;
animation: hint-fade 2s ease-in-out infinite alternate;
}
@keyframes hint-fade {
from {
opacity: 0.4;
}
to {
opacity: 0.9;
}
}
</style>
</head>
<body>
<div class="stars"></div>
<div class="corner-glow tl"></div>
<div class="corner-glow tr"></div>
<div class="corner-glow br"></div>
<div class="corner-glow bl"></div>
<div class="track"></div>
<div class="center">
<h1>ORBIT</h1>
<p>Pure CSS3 · No JavaScript</p>
</div>
<div class="trail trail-8"></div>
<div class="trail trail-7"></div>
<div class="trail trail-6"></div>
<div class="trail trail-5"></div>
<div class="trail trail-4"></div>
<div class="trail trail-3"></div>
<div class="trail trail-2"></div>
<div class="trail trail-1"></div>
<div class="cursor"></div>
<div class="hint">Pure CSS3 Animation · cursor orbiting the screen</div>
</body>
</html>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>光标围绕屏幕旋转动效</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100vw;
height: 100vh;
background: #0a0a0f;
overflow: hidden;
cursor: none;
font-family: 'Segoe UI', sans-serif;
display: flex;
align-items: center;
justify-content: center;
}
.center-text {
text-align: center;
user-select: none;
pointer-events: none;
z-index: 1;
}
.center-text h1 {
font-size: clamp(2rem, 5vw, 4rem);
font-weight: 700;
background: linear-gradient(135deg, #a78bfa, #60a5fa, #34d399);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: 0.05em;
margin-bottom: 0.5rem;
}
.center-text p {
color: rgba(255,255,255,0.3);
font-size: 1rem;
letter-spacing: 0.2em;
text-transform: uppercase;
}
.border-glow {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
.border-glow::before {
content: '';
position: absolute;
inset: 12px;
border-radius: 16px;
border: 1px solid rgba(167, 139, 250, 0.08);
box-shadow:
inset 0 0 60px rgba(167,139,250,0.03),
0 0 60px rgba(167,139,250,0.03);
}
#cursor {
position: fixed;
width: 18px;
height: 18px;
border-radius: 50%;
pointer-events: none;
z-index: 1000;
transform: translate(-50%, -50%);
mix-blend-mode: screen;
}
#trail-canvas {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 999;
}
#path-canvas {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 2;
}
#bg-canvas {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 0;
}
.info {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
color: rgba(255,255,255,0.15);
font-size: 0.7rem;
letter-spacing: 0.15em;
text-transform: uppercase;
pointer-events: none;
z-index: 10;
}
</style>
</head>
<body>
<canvas id="bg-canvas"></canvas>
<canvas id="path-canvas"></canvas>
<canvas id="trail-canvas"></canvas>
<div class="border-glow"></div>
<div class="center-text">
<h1>ORBIT</h1>
<p>Cursor Animation</p>
</div>
<div id="cursor"></div>
<div class="info" id="info">angle: 0°</div>
<script>
(() => {
const CFG = {
padding: 48,
speed: 0.8,
trailLength: 60,
trailFade: 0.92,
bgParticles: 80,
};
const $ = id => document.getElementById(id);
const trailCanvas = $('trail-canvas');
const pathCanvas = $('path-canvas');
const bgCanvas = $('bg-canvas');
const trailCtx = trailCanvas.getContext('2d');
const pathCtx = pathCanvas.getContext('2d');
const bgCtx = bgCanvas.getContext('2d');
function resize() {
[trailCanvas, pathCanvas, bgCanvas].forEach(c => {
c.width = window.innerWidth;
c.height = window.innerHeight;
});
initBgParticles();
drawPath();
}
window.addEventListener('resize', resize);
function getRectPoint(t) {
const pad = CFG.padding;
const W = window.innerWidth;
const H = window.innerHeight;
const x0 = pad, y0 = pad;
const x1 = W - pad, y1 = H - pad;
const w = x1 - x0, h = y1 - y0;
const perimeter = 2 * (w + h);
const dist = t * perimeter;
if (dist <= w) {
return { x: x0 + dist, y: y0, tx: 1, ty: 0 };
} else if (dist <= w + h) {
const d = dist - w;
return { x: x1, y: y0 + d, tx: 0, ty: 1 };
} else if (dist <= 2*w + h) {
const d = dist - w - h;
return { x: x1 - d, y: y1, tx: -1, ty: 0 };
} else {
const d = dist - 2*w - h;
return { x: x0, y: y1 - d, tx: 0, ty: -1 };
}
}
function drawPath() {
const ctx = pathCtx;
ctx.clearRect(0, 0, pathCanvas.width, pathCanvas.height);
ctx.save();
ctx.setLineDash([6, 14]);
ctx.lineWidth = 1;
ctx.strokeStyle = 'rgba(167,139,250,0.10)';
ctx.beginPath();
const steps = 300;
for (let i = 0; i <= steps; i++) {
const pt = getRectPoint(i / steps);
i === 0 ? ctx.moveTo(pt.x, pt.y) : ctx.lineTo(pt.x, pt.y);
}
ctx.closePath();
ctx.stroke();
ctx.restore();
}
let bgParticles = [];
function initBgParticles() {
bgParticles = Array.from({ length: CFG.bgParticles }, () => ({
x: Math.random() * window.innerWidth,
y: Math.random() * window.innerHeight,
r: Math.random() * 1.5 + 0.3,
a: Math.random(),
speed: Math.random() * 0.003 + 0.001,
phase: Math.random() * Math.PI * 2,
}));
}
function drawBg(time) {
const ctx = bgCtx;
ctx.clearRect(0, 0, bgCanvas.width, bgCanvas.height);
bgParticles.forEach(p => {
const alpha = p.a * (0.4 + 0.6 * Math.sin(time * p.speed + p.phase));
ctx.beginPath();
ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
ctx.fillStyle = `rgba(167,139,250,${alpha})`;
ctx.fill();
});
}
let trail = [];
function addTrailPoint(x, y, t) {
trail.push({ x, y, t });
if (trail.length > CFG.trailLength) trail.shift();
}
function drawTrail() {
const ctx = trailCtx;
ctx.clearRect(0, 0, trailCanvas.width, trailCanvas.height);
for (let i = 1; i < trail.length; i++) {
const ratio = i / trail.length;
const alpha = ratio * ratio;
const width = ratio * 6 + 0.5;
const hue = 260 + ratio * 80;
ctx.save();
ctx.beginPath();
ctx.moveTo(trail[i - 1].x, trail[i - 1].y);
ctx.lineTo(trail[i].x, trail[i].y);
ctx.strokeStyle = `hsla(${hue}, 90%, 70%, ${alpha})`;
ctx.lineWidth = width;
ctx.lineCap = 'round';
ctx.shadowColor = `hsla(${hue}, 100%, 70%, ${alpha * 0.6})`;
ctx.shadowBlur = 12;
ctx.stroke();
ctx.restore();
}
if (trail.length > 0) {
const head = trail[trail.length - 1];
ctx.save();
const grad = ctx.createRadialGradient(head.x, head.y, 0, head.x, head.y, 18);
grad.addColorStop(0, 'rgba(200,180,255,0.9)');
grad.addColorStop(0.4, 'rgba(120,160,255,0.5)');
grad.addColorStop(1, 'rgba(100,220,200,0)');
ctx.beginPath();
ctx.arc(head.x, head.y, 18, 0, Math.PI * 2);
ctx.fillStyle = grad;
ctx.fill();
ctx.restore();
}
}
const cursorEl = $('cursor');
const infoEl = $('info');
function updateCursorEl(x, y, tx, ty) {
const angle = Math.atan2(ty, tx) * 180 / Math.PI;
cursorEl.style.left = x + 'px';
cursorEl.style.top = y + 'px';
const hue = (360 * progress + performance.now() * 0.05) % 360;
cursorEl.style.background =
`radial-gradient(circle at 40% 40%, hsl(${hue},100%,85%), hsl(${(hue+60)%360},90%,55%))`;
cursorEl.style.boxShadow =
`0 0 20px 6px hsl(${hue},100%,65%), 0 0 40px 12px hsl(${(hue+60)%360},80%,40%)`;
cursorEl.style.transform =
`translate(-50%, -50%) rotate(${angle}deg) scaleX(1.5)`;
}
let progress = 0;
let lastTime = null;
function tick(time) {
if (!lastTime) lastTime = time;
const dt = Math.min(time - lastTime, 50);
lastTime = time;
const W = window.innerWidth, H = window.innerHeight;
const perimeter = 2 * (W - 2*CFG.padding + H - 2*CFG.padding);
const pixelsPerFrame = (CFG.speed / 100) * perimeter * (dt / 16.67);
progress += pixelsPerFrame / perimeter;
if (progress >= 1) progress -= 1;
const pt = getRectPoint(progress);
updateCursorEl(pt.x, pt.y, pt.tx, pt.ty);
addTrailPoint(pt.x, pt.y, time);
drawBg(time);
drawTrail();
const deg = Math.round(progress * 360);
infoEl.textContent = `progress: ${Math.round(progress * 100)}%`;
requestAnimationFrame(tick);
}
resize();
requestAnimationFrame(tick);
window.addEventListener('wheel', e => {
CFG.speed = Math.max(0.2, Math.min(5, CFG.speed + (e.deltaY > 0 ? 0.1 : -0.1)));
});
window.addEventListener('click', () => {
const orig = CFG.speed;
CFG.speed = Math.min(8, orig * 3);
setTimeout(() => { CFG.speed = orig; }, 600);
});
})();
</script>
</body>
</html>