如何自定义CSS呼吸节奏器的吸气、屏息、呼气各阶段时长?
自定义呼吸节奏器的时长控制方案
原代码通过alternate infinite实现吸气/呼气的交替循环,但无法控制中间的屏息时长。要实现吸气→吸气后屏息→呼气→呼气后屏息的完整可控周期,需要重构动画关键帧,将整个呼吸循环拆分为四个阶段,并通过CSS变量统一管理各阶段时长。
实现思路
- 用CSS变量定义各阶段时长,方便快速调整
- 计算总周期时长,将各阶段时长转换为关键帧的百分比位置
- 重写所有动画的关键帧,在对应百分比位置定义状态:吸气阶段放大/位移,屏息阶段保持状态,呼气阶段缩小/复位,呼气后屏息保持最小状态
修改后的完整代码
CSS部分
/* 定义各阶段时长,直接修改这里即可调整节奏 */ :root { --inhale: 4s; /* 吸气时长 */ --hold-after-inhale: 2s; /* 吸气后屏息时长 */ --exhale: 6s; /* 呼气时长 */ --hold-after-exhale: 2s; /* 呼气后屏息时长 */ --total-duration: calc(var(--inhale) + var(--hold-after-inhale) + var(--exhale) + var(--hold-after-exhale)); } body { background: #000; display: flex; align-items: center; height: 100vh; justify-content: center; } .watch-face { height: 125px; width: 125px; /* 使用总周期时长,循环播放 */ animation: pulse var(--total-duration) cubic-bezier(0.5, 0, 0.5, 1) infinite; } .circle { height: 125px; width: 125px; border-radius: 50%; position: absolute; mix-blend-mode: screen; transform: translate(0, 0); } .circle:nth-child(odd) { background: #61bea2; } .circle:nth-child(even) { background: #529ca0; } /* 所有circle动画统一使用总周期 */ .circle:nth-child(1) { animation: circle-1 var(--total-duration) ease infinite; } .circle:nth-child(2) { animation: circle-2 var(--total-duration) ease infinite; } .circle:nth-child(3) { animation: circle-3 var(--total-duration) ease infinite; } .circle:nth-child(4) { animation: circle-4 var(--total-duration) ease infinite; } .circle:nth-child(5) { animation: circle-5 var(--total-duration) ease infinite; } .circle:nth-child(6) { animation: circle-6 var(--total-duration) ease infinite; } /* 重写pulse关键帧,包含四个阶段 */ @keyframes pulse { /* 初始状态:呼气后屏息的最小状态 */ 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: scale(.15) rotate(180deg); } /* 吸气结束:达到最大状态 */ calc(var(--inhale)/var(--total-duration)*100%) { transform: scale(1); } /* 吸气后屏息阶段:保持最大状态 */ calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: scale(1); } /* 呼气阶段:从最大回到最小 */ calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: scale(.15) rotate(180deg); } /* 呼气后屏息阶段:保持最小状态,回到初始 */ 100% { transform: scale(.15) rotate(180deg); } } /* 重写circle动画,同步四个阶段的位移 */ @keyframes circle-1 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(-35px, -50px); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(-35px, -50px); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } } @keyframes circle-2 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(35px, 50px); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(35px, 50px); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } } @keyframes circle-3 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(-60px, 0); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(-60px, 0); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } } @keyframes circle-4 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(60px, 0); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(60px, 0); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } } @keyframes circle-5 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(-35px, 50px); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(-35px, 50px); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } } @keyframes circle-6 { 0%, calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } calc(var(--inhale)/var(--total-duration)*100%) { transform: translate(35px, -50px); } calc((var(--inhale) + var(--hold-after-inhale))/var(--total-duration)*100%) { transform: translate(35px, -50px); } calc((var(--inhale) + var(--hold-after-inhale) + var(--exhale))/var(--total-duration)*100%) { transform: translate(0, 0); } 100% { transform: translate(0, 0); } }
HTML部分
<div class="watch-face"> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> <div class="circle"></div> </div>
使用说明
直接修改:root中的四个变量值,即可分别控制各阶段时长:
--inhale:吸气时长--hold-after-inhale:吸气后屏息时长--exhale:呼气时长--hold-after-exhale:呼气后屏息时长
所有动画会自动同步总周期,保持呼吸节奏的一致性。
内容的提问来源于stack exchange,提问作者gal
相关产品推荐
相关产品推荐

