likes
comments
collection
share

css动画浅尝试说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于

作者站长头像
站长
· 阅读数 17

✊不积跬步,无以至千里;不积小流,无以成江海

说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于自我挑战的目的,我努力尝试三个动画全部都自己实现(并且也做到了哈哈哈)记录一下~

图源设计稿~

倒计时动画

需求如图,希望在一个弹窗中展示倒计时,外圈蓝色伴随时间消失剩余白色部分,并同步中间数字。

css动画浅尝试说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于

代码思路:

使用两个 circle 元素包裹在一个 svg 元素中实现倒计时圆环的效果。具体原因如下:

  1. 背景圆环:第一个 circle 元素用于绘制背景圆环,表示整个倒计时的总长度。这个圆环通常是一个完整的圆,颜色较浅或透明度较高,以便作为背景。
  2. 进度圆环:第二个 circle 元素用于绘制进度圆环,表示当前倒计时的进度。通过调整 strokeDasharray 和 strokeDashoffset 属性,可以动态改变这个圆环的长度,从而实现倒计时的视觉效果。

为什么不使用div而使用svg? SVG(Scalable Vector Graphics)一种用于描述二维矢量图形的XML语言。

  • 它允许创建高质量、可缩放的图形,而不会失真。相比之下,HTML中的 div 元素主要用于布局和容器,不适合绘制复杂的图形。
  • 精确控制:SVG 提供了对图形元素(如圆形、矩形、路径等)的精确控制。通过使用 SVG,可以轻松地控制图形的大小、位置、颜色、边框等属性,而这些在使用 div 时是很难实现的。

倒计时居中:使用绝对定位,并控制z-index.

倒计时思路:

  1. 状态初始化: 初始化为 totalTime(在这个例子中是 60 秒)。
  2. Effect Hook: 使用 useEffect 钩子设置一个定时器,每秒将 progress 减少 1。如果 progress 大于 0,则减 1。如果 progress 到达 0,则清除定时器。
  3. 圆周长计算: 计算半径为 91 的圆的周长。
  4. 进度值计算: 根据剩余时间计算当前进度值,占总周长的比例。
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>
        );
    },
};

进度条动画

需求如图,希望在主屏幕上显示关机进度。随时间增加白色进度条。

css动画浅尝试说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于

代码思路:

使用radix-ui中的Progress组件来展示进度。根据官方文档,需要由root包裹一个Indicator。

倒计时思路:

  1. 初始化状态: progress 被初始化为 totalTime(30秒)。
  2. 使用 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度为一个周期。

css动画浅尝试说起来还是有点震颤,这竟然是我第一次根据需求写动画。虽然有很多有挑战性的可以在npm上找找插件,但是出于

代码思路:

SVG:

  1. defs 元素用于定义可重复使用的图形元素,如渐变、图案等。它本身不会被渲染,但可以被其他元素引用。
  2. linearGradient 元素定义了一个线性渐变,id 为 gradient。x1="0%" y1="0%" x2="100%" y2="100%" 定义了渐变的起点和终点,分别为左上角和右下角。
  3. stop 元素定义了渐变的颜色和位置。offset="0%" stopColor="#colorA" 表示渐变从 #colorA 开始,offset="100%" stopColor="white" 表示渐变在 100% 处结束为白色。
  4. circle 元素定义了一个圆形,cx="100" cy="100" r="96" 分别表示圆心的 x 坐标、y 坐标和半径。
  5. 将定义的渐变应用到圆形的描边上,使得圆形显示出从 #colorA 到 white 的渐变效果。

图标: WindowIcon 是一个 SVG 图标,定位在 div 的中心。

动画的实现:

  1. @keyframes 定义了一个名为 spin 的动画。from { transform: rotate(0deg); } 表示动画从 0 度开始旋转。to { transform: rotate(360deg); } 表示动画旋转到 360 度结束。
  2. animation: spin 2s linear infinite; 将 spin 动画应用到具有 spinner 类的元素上。 2s 表示动画持续时间为 2 秒。linear 表示动画的速度曲线为线性。infinite 表示动画无限循环。
  3. 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
评论
请登录