如何制作带自定义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> ); };
实现要点
- 自定义样式:通过props传递
minWidth、height、borderRadius参数,灵活配置椭圆形尺寸 - 进度动画:利用
::before伪元素作为进度层,通过rotate变换和clip-path裁剪,实现顺时针填充效果 - 计时器逻辑:用
useState维护剩余时间和进度值,useEffect创建定时器每秒更新状态 - 层级管理:通过
z-index确保文字始终显示在最上层,::after伪元素保留未填充区域的边框样式
内容的提问来源于stack exchange,提问作者David Cho
相关产品推荐
相关产品推荐

