-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
2020-03-20 实现一个倒计时工具 #14
Comments
|
CountDown import React, { useEffect, useMemo, useRef } from 'react';
import { CountDownProps, CountDownComponent } from './types';
import { useTime, defaultTransform } from './utils';
let timer: any;
const CountDown: CountDownComponent<CountDownProps> = ({
endTime = 0,//结束日期:日期对象或者是时间戳
hours = 0,//倒计时多少小时
minutes = 0,//倒计时多少分钟
seconds = 0,//倒计时多少秒
onTimeout,//时间到的事件处理
style,//样式
transform = defaultTransform,//返回的react元素将被渲染
onChange,//一秒触发一次
}: CountDownProps) => {
const { current: timeMap } = useRef({ endTime, hours, minutes, seconds });
const remainingSeconds: number = useMemo(() => {
let { endTime, hours, minutes, seconds } = timeMap;
if (!endTime) {
return hours * 60 * 60 + minutes * 60 + seconds;
} else {
endTime = typeof endTime === "number" ? new Date(endTime) : endTime;
return Math.round((endTime.getTime() - Date.now()) / 1000);
}
}, [timeMap]);
const diffObj = useRef(remainingSeconds);
const [timeObj, setTime] = useTime(remainingSeconds);
useEffect(() => {
let remainingSeconds = diffObj.current;
const timerHandler = () => {
if (remainingSeconds === 0) {
clearInterval(timer);
return onTimeout();
}
remainingSeconds -= 1;
if (typeof onChange === 'function') {
const res = onChange(remainingSeconds);
switch (typeof res) {
case 'number':
remainingSeconds = res;
break;
case 'boolean':
if (!res) {
return clearInterval(timer);
}
}
}
setTime(remainingSeconds);
}
timer = setInterval(timerHandler, 1000);
CountDown.continue = () => {
timer = setInterval(timerHandler, 1000);
}
return () => clearInterval(timer);
}, [onTimeout, setTime, onChange]);
const jsx = useMemo(() => transform(timeObj, diffObj.current), [transform, timeObj, diffObj]);
return (
<span style={style}>{jsx}</span>
)
}
CountDown.pause = () => {
clearInterval(timer);
}
export default CountDown; utils import { Transform, UseTime } from './types';
import { useState } from 'react';
export const patchZero = (s: string) => s.length < 2 ? '0' + s : s;
export const defaultTransform: Transform = ({ hours, minutes, seconds }, diffSeconds: number) => {
return `${patchZero(String(hours))}:${patchZero(String(minutes))}:${patchZero(String(seconds))}`;
}
export const useTime: UseTime = (diffSeconds) => {
const [time, setTime] = useState(diffSeconds);
let current = time;
const seconds = current % 60;
current -= seconds;
const minutes = current / 60 % 24;
current -= minutes * 60;
const hours = Math.trunc(current / 60 / 60);
return [
{ hours, minutes, seconds },
setTime
]
}
types import { FC } from 'react';
export interface TimeObj {
hours: number,
minutes: number,
seconds: number
}
export type CountDownComponent<T> = {
pause: () => void
continue?: () => void
} & FC<T>
export interface CountDownProps {
endTime?: Date | number,
onTimeout: Function
style?: React.CSSProperties,
transform?: Transform,
hours?: number,
minutes?: number,
seconds?: number,
onChange?: (remainingSeconds: number) => number | boolean | void
}
export type Transform = (timeObj: TimeObj, diffSeconds: number) => React.ReactNode;
export type UseTime = (diffSeconds: number) => [
TimeObj,
Function
]; |
class Timer {
constructor(props) {
const {
container, // 倒计时容器,dom元素
time, // 倒计时间,秒
onStart = () => {}, // 点击开始回调,可选
onStop = () => {}, // 点击暂停回调,可选
onChange = () => {}, // 每一秒回调,可选
onFinish = () => {}, // 倒计时结束回调,可选
onReset = () => {} // 点击重置回调,可选
} = props;
this.container = container;
this.curTime = this.totalTime = time;
this.gapTime = 1000;
this.onStart = onStart;
this.onStop = onStop;
this.onChange = onChange;
this.onFinish = onFinish;
this.onReset = onReset;
this.stopFlag = false;
this.timeBox = document.createElement("div");
}
init() {
this.timeBox.innerText = this.formatTime();
this.container.append(this.timeBox);
this.printBtns();
}
printBtns() {
const startBtn = document.createElement("button");
startBtn.setAttribute("class", "operator-btn");
startBtn.innerText = "开始";
startBtn.addEventListener("click", e => {
this.container.replaceChild(stopBtn, e.target);
this.start();
this.onStart();
});
const stopBtn = document.createElement("button");
stopBtn.setAttribute("class", "operator-btn");
stopBtn.innerText = "暂停";
stopBtn.addEventListener("click", e => {
this.container.replaceChild(startBtn, e.target);
this.stop();
this.onStop();
});
const resetBtn = document.createElement("button");
resetBtn.innerText = "重置";
resetBtn.addEventListener("click", e => {
if (!this.stopFlag) {
this.stop();
this.container.replaceChild(
startBtn,
this.container.querySelector(".operator-btn")
);
}
this.resetFlag = true;
this.curTime = this.totalTime + 1;
this.printTime();
this.onReset();
});
this.container.append(startBtn);
this.container.append(resetBtn);
}
start() {
this.stopFlag = false;
let raf;
let startTime = Date.now();
let endTime;
const second = () => {
raf = requestAnimationFrame(second);
if (this.stopFlag) {
cancelAnimationFrame(raf);
// 继续计时
if (!this.resetFlag) {
this.gapTime -= Date.now() - startTime;
}
return;
}
endTime = Date.now();
// 每隔1秒显示
if (endTime - startTime < this.gapTime) {
return;
}
if (this.curTime <= 1) {
cancelAnimationFrame(raf);
this.finish();
return;
}
this.printTime();
this.onChange();
startTime = Date.now();
};
raf = requestAnimationFrame(second);
}
stop() {
this.stopFlag = true;
}
finish() {
this.timeBox.innerText = "时间到!";
this.container.replaceChild(
this.timeBox,
this.container.querySelector("div")
);
this.onFinish();
}
printTime() {
this.timeBox.innerText = this.formatTime(--this.curTime);
this.container.replaceChild(
this.timeBox,
this.container.querySelector("div")
);
this.gapTime = 1000;
}
formatTime(seconds = this.curTime) {
const sec = seconds % 60;
const min = Math.trunc(seconds / 60) % 60;
const hour = Math.trunc(seconds / 60 / 60) % 60;
const day = Math.trunc(seconds / 60 / 60 / 24) % 60;
return (
(day ? day + "天" : "") +
(hour ? hour + "小时" : "") +
(min ? min + "分钟" : "") +
(sec + "秒")
);
}
}
function countdown(props) {
const box = new Timer(props);
box.init();
}
export default countdown; |
其实不需要有UI部分,只需要提供API出来,不管是VUE React 还是不需要用到UI的程序都可以来用。 |
向外暴露4个方法
class Timer {
gapTime = 1000;
stopFlag = false;
resetFlag = false;
init(time, onChange, onFinish) {
this.curTime = this.totalTime = time;
this.onChange = onChange;
this.onFinish = onFinish;
}
start() {
this.stopFlag = false;
this.resetFlag = false;
let raf;
let startTime = Date.now();
let endTime;
const second = () => {
raf = requestAnimationFrame(second);
if (this.stopFlag) {
cancelAnimationFrame(raf);
// 继续计时
if (!this.resetFlag) {
this.gapTime -= Date.now() - startTime;
}
return;
}
endTime = Date.now();
// 每隔1秒显示
if (endTime - startTime < this.gapTime) {
return;
}
if (this.curTime <= 1) {
cancelAnimationFrame(raf);
this.onFinish();
return;
}
this.onChange(--this.curTime);
this.gapTime = 1000
startTime = Date.now();
};
raf = requestAnimationFrame(second);
}
stop() {
this.stopFlag = true;
}
reset() {
if (!this.stopFlag) {
this.stop();
}
this.resetFlag = true;
this.curTime = this.totalTime;
}
}
export default new Timer(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
设计一个倒计时工具,考虑稳定性、复用性、可用性。
相当于自己沉淀一个倒计时组件,未来工作中可以直接拿过来用。
做到简单易懂易用,未来进入公司工作到时候,遇到倒计时需求的时候,可以自信到把现在写的代码拿出来用,或许那时候已经是一个好用的npm包了。
The text was updated successfully, but these errors were encountered: