css动画浅尝试说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于
✊不积跬步,无以至千里;不积小流,无以成江海
说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于自我挑战的目的,我努力尝试三个动画全部都自己实现(并且也做到了哈哈哈)记录一下~
图源设计稿~
倒计时动画
需求如图,希望在一个弹窗中展示倒计时,外圈蓝色伴随时间消失剩余白色部分,并同步中间数字。
代码思路:
使用两个 circle 元素包裹在一个 svg 元素中实现倒计时圆环的效果。具体原因如下:
- 背景圆环:第一个 circle 元素用于绘制背景圆环,表示整个倒计时的总长度。这个圆环通常是一个完整的圆,颜色较浅或透明度较高,以便作为背景。
- 进度圆环:第二个 circle 元素用于绘制进度圆环,表示当前倒计时的进度。通过调整 strokeDasharray 和 strokeDashoffset 属性,可以动态改变这个圆环的长度,从而实现倒计时的视觉效果。
为什么不使用div而使用svg? SVG(Scalable Vector Graphics)一种用于描述二维矢量图形的XML语言。
- 它允许创建高质量、可缩放的图形,而不会失真。相比之下,HTML中的 div 元素主要用于布局和容器,不适合绘制复杂的图形。
- 精确控制:SVG 提供了对图形元素(如圆形、矩形、路径等)的精确控制。通过使用 SVG,可以轻松地控制图形的大小、位置、颜色、边框等属性,而这些在使用 div 时是很难实现的。
倒计时居中:使用绝对定位,并控制z-index.
倒计时思路:
- 状态初始化: 初始化为 totalTime(在这个例子中是 60 秒)。
- Effect Hook: 使用 useEffect 钩子设置一个定时器,每秒将 progress 减少 1。如果 progress 大于 0,则减 1。如果 progress 到达 0,则清除定时器。
- 圆周长计算: 计算半径为 91 的圆的周长。
- 进度值计算: 根据剩余时间计算当前进度值,占总周长的比例。
export const CountDown60s: Story = {
render: () => {
const totalTime = 60;
const [progress, setProgress] = useState(totalTime);
useEffect(() => {
const timer = setInterval(() => {
setProgress((progress) => {
if (progress > 0) {
return progress - 1;
} else {
clearInterval(timer);
return progress;
}
});
}, 1000);
}, []);
const circumference = 2 * Math.PI * 91;
const progressValue = (progress / totalTime) * circumference;
return (
<div className="flex w-[XXpx] flex-col p-10 pb-6">
<div className="mb-6 text-[26px] font-medium leading-[32px] text-white">
Title
</div>
<div className="mb-14 text-[#ColorA]">
Discription
</div>
<div className="relative mb-16 flex items-center justify-center">
<svg
className="size-[180px]"
viewBox="0 0 184 184"
fill="none"
style={{ transform: "rotate(-90deg)" }}
>
<circle
strokeWidth="2"
stroke="white"
fill="transparent"
r="91"
cx="92"
cy="92"
/>
<circle
strokeWidth="2"
stroke="#ColorB"
strokeDasharray={circumference}
strokeDashoffset={progressValue - circumference}
strokeLinecap="round"
fill="transparent"
r="91"
cx="92"
cy="92"
/>
</svg>
<div className="absolute left-1/2 top-1/2 z-20 -translate-x-1/2 -translate-y-1/2 transform text-[100px] font-normal leading-[144px] text-white">
{progress}
</div>
</div>
</div>
);
},
};
进度条动画
需求如图,希望在主屏幕上显示关机进度。随时间增加白色进度条。
代码思路:
使用radix-ui中的Progress组件来展示进度。根据官方文档,需要由root包裹一个Indicator。
倒计时思路:
- 初始化状态: progress 被初始化为 totalTime(30秒)。
- 使用 useEffect 设置定时器: 使用 useEffect 钩子设置一个定时器,每秒将 progress 减少 1。如果 progress 大于 0,则减 1。如果 progress 到达 0,则清除定时器。
这个逻辑实现了一个简单的倒计时,每秒减少 progress 的值,直到倒计时结束。
export const ShuttingDown30s = {
render: () => {
const totalTime = 30;
const [progress, setProgress] = useState(totalTime);
useEffect(() => {
const timer = setInterval(() => {
setProgress((progress) => {
if (progress > 0) {
return progress - 1;
} else {
clearInterval(timer);
return progress;
}
});
}, 1000);
}, []);
return (
<div className="flex w-[XXpx] flex-col items-center">
<div className="mb-4 text-[22px] font-medium leading-[28px] text-white">
Text
</div>
<div className="mb-12 text-white">
description
</div>
<Progress.Root
className="h-[12px] w-[360px] overflow-hidden rounded-full bg-black"
style={{
transform: "translateZ(0)",
}}
value={progress}
>
<Progress.Indicator
className="h-full bg-white"
style={{
width: `${(progress / totalTime) * 100}%`,
transition: "width 1s linear",
}}
/>
</Progress.Root>
</div>
);
},
};
windows 动画
需求如图,中间呈现windows图标,外圈蓝白过渡并循环,假设以白色为起点,白色从0度旋转360度为一个周期。
代码思路:
SVG:
- defs 元素用于定义可重复使用的图形元素,如渐变、图案等。它本身不会被渲染,但可以被其他元素引用。
- linearGradient 元素定义了一个线性渐变,id 为 gradient。x1="0%" y1="0%" x2="100%" y2="100%" 定义了渐变的起点和终点,分别为左上角和右下角。
- stop 元素定义了渐变的颜色和位置。offset="0%" stopColor="#colorA" 表示渐变从 #colorA 开始,offset="100%" stopColor="white" 表示渐变在 100% 处结束为白色。
- circle 元素定义了一个圆形,cx="100" cy="100" r="96" 分别表示圆心的 x 坐标、y 坐标和半径。
- 将定义的渐变应用到圆形的描边上,使得圆形显示出从 #colorA 到 white 的渐变效果。
图标: WindowIcon 是一个 SVG 图标,定位在 div 的中心。
动画的实现:
- @keyframes 定义了一个名为 spin 的动画。from { transform: rotate(0deg); } 表示动画从 0 度开始旋转。to { transform: rotate(360deg); } 表示动画旋转到 360 度结束。
- animation: spin 2s linear infinite; 将 spin 动画应用到具有 spinner 类的元素上。 2s 表示动画持续时间为 2 秒。linear 表示动画的速度曲线为线性。infinite 表示动画无限循环。
- className="spinner"。将定义的旋转动画应用到这个 SVG 元素上,使其进行旋转。
export const WindowsAnimation: Story = {
render: () => {·
return (
<div className="relative flex w-[XXpx] flex-col items-center">
<svg className="spinner h-[XXpx] w-[XXpx]" viewBox="0 0 200 200">
<defs>
<linearGradient id="gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#colorA" />
<stop offset="100%" stopColor="white" />
</linearGradient>
</defs>
<circle cx="100" cy="100" r="96" className="gradient-circle"></circle>
</svg>
<WindowIcon className="absolute left-1/2 top-1/2 z-20 -translate-x-1/2 -translate-y-1/2 transform" />
</div>
);
},
};
//
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
.spinner {
animation: spin 2s linear infinite;
}
.gradient-circle {
stroke: url(#gradient);
stroke-width: 8;
fill: none;
}
这个动画看似简单,其实耗时最久,因为中间图标背景要求是透明的(不影响背景的变化显示)。
首先如果用常用的解决圆环的方法来解决这个不适用,因为大众解决方法是一个圆+背景覆盖,这个需求留出背景。
其次用border+border-e也不行,因为这个是渐变,border-e是颜色直接变化,没有渐变的过渡。
之后尝试过用自定义border也不行。【即使用 ::before 和 ::after 伪元素来创建自定义边框。在伪元素上应用渐变背景并调整位置,确保伪元素的边框在圆的后面。】因为自定义border会默认放置在最前面,rounder-full也并没办法覆盖。还是没办法正确显示背景。
最后用defs覆盖了一个linearGradient,再把动画加在外面。
转载自:https://juejin.cn/post/7402110528297304104