You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何制作带自定义border-radius的椭圆形60秒顺时针计时器?

椭圆形计时器实现方案

需求说明

  • 支持自定义border-radius
  • 计时器按顺时针方向运行60秒
  • 已流逝的时间区域显示#222背景色

现有基础代码

import styled from 'styled-components';
import classNames from 'classnames';

const Container = styled.button`
  position: relative;
  min-width: 520px;
  height: 220px;
  border-radius: 220px;
  background-color: transparent;
  border-width: 30px;
  border-style: solid;
  border-color: var(--color);
  color: var(--color);
  font-size: 68px;
  font-weight: 600;

  &.timer {
    border-color: ${colors.charcoal};
  }
`;

export const Button = ({ className, buttonTimer, onClick, children }) => {
  return (
    <Container
      className={classNames(className, {
        timer: buttonTimer > 0,
      })}
      type='button'
      onClick={onClick}
      style={{ color: '#00dab6' }}
    >
      {children}
    </Container>
  );
};

概念图

计时器概念图

完整实现代码

基于现有代码,通过伪元素结合CSS变换、裁剪逻辑,再集成计时器状态管理,实现需求效果:

import styled from 'styled-components';
import classNames from 'classnames';
import { useEffect, useState } from 'react';

const Container = styled.button`
  position: relative;
  min-width: ${props => props.minWidth || '520px'};
  height: ${props => props.height || '220px'};
  border-radius: ${props => props.borderRadius || '220px'};
  background-color: transparent;
  border-width: 30px;
  border-style: solid;
  border-color: #ddd;
  color: #00dab6;
  font-size: 68px;
  font-weight: 600;
  overflow: hidden;
  cursor: pointer;

  &.timer {
    border-color: transparent;
  }

  // 已流逝时间的进度层
  &.timer::before {
    content: '';
    position: absolute;
    top: -30px;
    left: -30px;
    width: calc(100% + 60px);
    height: calc(100% + 60px);
    border-radius: inherit;
    background-color: #222;
    transform-origin: center center;
    transform: rotate(${props => `-${(1 - props.progress) * 360}deg`});
    clip-path: polygon(50% 50%, 50% 0%, 100% 0%, 100% 100%, 50% 100%);
    transition: transform 1s linear;
  }

  // 未流逝区域的边框覆盖层
  &.timer::after {
    content: '';
    position: absolute;
    top: -30px;
    left: -30px;
    width: calc(100% + 60px);
    height: calc(100% + 60px);
    border-radius: inherit;
    background-color: transparent;
    border: 30px solid #ddd;
    z-index: 1;
  }

  // 确保计时器文字在最上层
  & > span {
    position: relative;
    z-index: 2;
  }
`;

export const Button = ({ 
  className, 
  minWidth = '520px',
  height = '220px',
  borderRadius = '220px',
  onClick, 
  children 
}) => {
  const [buttonTimer, setButtonTimer] = useState(0);
  const [progress, setProgress] = useState(0);

  useEffect(() => {
    let timerInterval;
    if (buttonTimer > 0) {
      timerInterval = setInterval(() => {
        setButtonTimer(prev => {
          const newTime = prev - 1;
          setProgress(1 - newTime / 60);
          return newTime > 0 ? newTime : 0;
        });
      }, 1000);
    }
    return () => clearInterval(timerInterval);
  }, [buttonTimer]);

  const handleStart = () => {
    if (buttonTimer === 0) {
      setButtonTimer(60);
      setProgress(0);
    }
    onClick?.();
  };

  return (
    <Container
      className={classNames(className, {
        timer: buttonTimer > 0,
      })}
      type='button'
      onClick={handleStart}
      minWidth={minWidth}
      height={height}
      borderRadius={borderRadius}
      progress={progress}
    >
      <span>{buttonTimer > 0 ? buttonTimer : children || '开始'}</span>
    </Container>
  );
};

实现要点

  1. 自定义样式:通过props传递minWidth、height、borderRadius参数,灵活配置椭圆形尺寸
  2. 进度动画:利用::before伪元素作为进度层,通过rotate变换和clip-path裁剪,实现顺时针填充效果
  3. 计时器逻辑:用useState维护剩余时间和进度值,useEffect创建定时器每秒更新状态
  4. 层级管理:通过z-index确保文字始终显示在最上层,::after伪元素保留未填充区域的边框样式

内容的提问来源于stack exchange,提问作者David Cho

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.17 16:10:29