RGB 呼吸光边
oryzo.ai 手持杯垫那幕的名场面:整个视口被一圈流动的 RGB 光框住 —— "AI 硬件"当然得有电竞灯(页面下文正好是 "Runs on RTX 3090" 的玩梗卡)。 光边随时间转色、随呼吸明暗,hover 展品还会增压。复现是纯 CSS + 十行 JS。
要点
- 彩虹环 =
conic-gradient从角度变量--rgbA起转一圈七色再回到起点色(首尾同色才无缝) - 抠成"只剩边框"靠 mask 异或:
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)+mask-composite: exclude—— 整框减去内容盒,剩下 padding 那一圈就是边 - 辉光层和锐边层是同一套背景画两遍:厚 padding +
blur(26px)的在下当氛围,薄 padding +blur(1px)的在上当轮廓 —— 双层是"发光体"质感的关键,单层 blur 只像贴纸 - 让渐变的起始角能被 CSS 动画插值,必须先
@property --rgbA { syntax: '<angle>' }注册 —— 否则@keyframes里改自定义属性是跳变不是旋转;不支持的浏览器优雅退化成静态彩虹边 - 呼吸 = 慢速 opacity 正弦;hover 增压走 JS 调一个
--glow变量,两层同时吃到 —— 变量是 CSS 和 JS 之间最顺的遥控器 - 光边
pointer-events: none且不参与布局(fixed + inset 0),对页面是纯"外挂灯",可以随场景整体淡入淡出 prefers-reduced-motion下停转停呼吸,留静态彩虹 —— 灯可以在,别闪
源码(折叠)
rgb-glow-border-demo.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>RGB 呼吸光边 · ORYZO 电竞灯效玩梗复现</title>
<style>
/* oryzo.ai 手持杯垫那幕:整个视口被一圈流动的 RGB 光边框住 ——
"AI 硬件"就得有电竞灯(页面下文正好是 "Runs on RTX 3090" 的玩梗卡)。
复现手法:conic-gradient 做彩虹环,mask 抠成"只剩边框",再 blur 成辉光。 */
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; background: #17110c; }
body { font-family: "DM Mono", ui-monospace, monospace; color: #f2e8d5; }
@property --rgbA { /* 注册成可动画的自定义属性:conic 起始角 */
syntax: '<angle>'; initial-value: 0deg; inherits: false;
}
.frame, .frame-sharp {
position: fixed; inset: 0; pointer-events: none;
background: conic-gradient(from var(--rgbA),
#ff3b30, #ff9500, #ffe14d, #34e07a, #38d1ff, #4d6bff, #c44dff, #ff3b30);
/* 两层 mask 异或:整框减去内容盒 = 只留 padding 那一圈 */
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
animation: spin 6s linear infinite;
}
.frame { padding: 34px; filter: blur(26px); opacity: var(--glow, .75); } /* 辉光层 */
.frame-sharp { padding: 5px; filter: blur(1px); opacity: calc(var(--glow, .75) * .9); } /* 锐边层 */
@keyframes spin { to { --rgbA: 360deg; } }
/* 呼吸:整体亮度慢波动(乘在 --glow 上由 JS 驱动更简单,这里给纯 CSS 兜底) */
.breath { animation: breath 3.4s ease-in-out infinite; }
@keyframes breath { 50% { opacity: .55; } }
/* 中央展品:CSS 画的软木杯垫(radial-gradient 立体感 + 噪点用重复渐变近似) */
.stage { position: fixed; inset: 0; display: grid; place-items: center; }
.coaster {
width: min(38vmin, 300px); aspect-ratio: 1; border-radius: 50%; cursor: pointer;
background:
radial-gradient(circle at 38% 32%, #d9a266 0%, #c98d4e 45%, #8a5a2b 78%, #6e441f 100%);
box-shadow: inset 0 0 0 6px #00000022, inset 0 -14px 30px #00000055,
0 30px 80px #000000aa;
position: relative; transition: transform .5s cubic-bezier(.2,1.4,.4,1);
}
.coaster::after { /* 凹陷内盘 */
content: ''; position: absolute; inset: 14%; border-radius: 50%;
background: radial-gradient(circle at 42% 36%, #b57f42, #8f5f2e 70%, #7a4e24);
box-shadow: inset 0 10px 24px #00000066;
}
.coaster:hover { transform: scale(1.05) rotate(-3deg); }
h2 { position: fixed; left: 50%; top: 12%; translate: -50% 0; font-size: clamp(26px, 4.5vw, 52px);
font-family: system-ui, sans-serif; font-weight: 650; letter-spacing: .02em; }
.hud { position: fixed; left: 16px; top: 14px; font-size: 12px; line-height: 1.7; letter-spacing: .06em; }
.hud b { font-size: 14px; letter-spacing: .12em; }
.hint { position: fixed; left: 50%; bottom: 16px; translate: -50% 0; font-size: 11px;
letter-spacing: .26em; opacity: .8; }
.tag { position: fixed; right: 16px; top: 14px; font-size: 11px; letter-spacing: .14em;
border: 1px solid #f2e8d566; border-radius: 99px; padding: 5px 12px; }
@media (prefers-reduced-motion: reduce) {
.frame, .frame-sharp { animation: none; }
.breath { animation: none; }
}
</style>
</head>
<body>
<div class="frame breath" id="glowLayer"></div>
<div class="frame-sharp"></div>
<div class="stage"><div class="coaster" id="coaster" title="hover 试试"></div></div>
<h2>Powered by AI*</h2>
<div class="hud"><b>RGB MODE</b><br>视口级电竞灯效 · conic-gradient 彩虹环</div>
<div class="tag">RUNS ON RTX 3090</div>
<div class="hint">TRY TO HOVER THE COASTER</div>
<script>
/* 交互:hover 展品时灯效增压(原站 hover 手部有反馈,"TRY TO HOVER HAND" 同款提示)。
* --rgbA 靠 @property 注册后才能被 CSS 动画插值;不支持的浏览器退化为静态彩虹边。
* hover 增压走 JS 调 --glow:CSS 变量一改,辉光层/锐边层一起吃到。 */
const coaster = document.getElementById('coaster');
const root = document.documentElement;
let boost = 0, target = 0;
coaster.addEventListener('pointerenter', () => target = 1);
coaster.addEventListener('pointerleave', () => target = 0);
(function tick() {
boost += (target - boost) * 0.12;
root.style.setProperty('--glow', (0.72 + boost * 0.55).toFixed(3));
requestAnimationFrame(tick);
})();
</script>
</body>
</html>