UNPKG

@nutui/nutui-react

Version:

京东风格的轻量级移动端 React 组件库,支持一套代码生成 H5 和小程序

202 lines (201 loc) 6.83 kB
import { _ as __rest } from "./tslib.es6.js"; import React__default, { useState, useRef, useImperativeHandle, useEffect } from "react"; import { C as ComponentDefaults } from "./typings.js"; import { p as padZero } from "./pad-zero.js"; const defaultProps = Object.assign(Object.assign({}, ComponentDefaults), { paused: false, startTime: Date.now(), endTime: Date.now(), remainingTime: 0, millisecond: false, format: "HH:mm:ss", autoStart: true, time: 0, destroy: false }); const InternalCountDown = (props, ref) => { const _a = Object.assign(Object.assign({}, defaultProps), props), { paused, startTime, endTime, remainingTime, millisecond, format, autoStart, time, destroy, className, style, onEnd, onPaused, onRestart, onUpdate, children } = _a, rest = __rest(_a, ["paused", "startTime", "endTime", "remainingTime", "millisecond", "format", "autoStart", "time", "destroy", "className", "style", "onEnd", "onPaused", "onRestart", "onUpdate", "children"]); const classPrefix = "nut-countdown"; const [restTimeStamp, setRestTime] = useState(0); const stateRef = useRef({ pauseTime: 0, curr: 0, isPaused: paused, isIninted: false, timer: 0, restTime: 0, // 倒计时剩余时间时间 counting: !paused && autoStart, // 是否处于倒计时中 handleEndTime: Date.now(), // 最终截止时间 diffTime: 0 // 设置了 startTime 时,与 date.now() 的差异 }); const getTimeStamp = (timeStr) => { if (!timeStr) return Date.now(); let t = timeStr; t = Number(t) > 0 ? +t : t.toString().replace(/-/g, "/"); return new Date(t).getTime(); }; const initTime = () => { if (remainingTime) { stateRef.current.handleEndTime = Date.now() + Number(remainingTime); } else { stateRef.current.handleEndTime = endTime; stateRef.current.diffTime = Date.now() - getTimeStamp(startTime); } if (!stateRef.current.counting) stateRef.current.counting = true; tick(); }; const tick = () => { stateRef.current.timer = requestAnimationFrame(() => { if (stateRef.current.counting) { const currentTime = Date.now() - stateRef.current.diffTime; const remainTime = Math.max(stateRef.current.handleEndTime - currentTime, 0); stateRef.current.restTime = remainTime; setRestTime(remainTime); if (!remainTime) { stateRef.current.counting = false; pause(); onEnd && onEnd(); } if (remainTime > 0) { tick(); } } }); }; const formatRemainTime = (t, type) => { const ts = t; const rest2 = { d: 0, h: 0, m: 0, s: 0, ms: 0 }; const SECOND = 1e3; const MINUTE = 60 * SECOND; const HOUR = 60 * MINUTE; const DAY = 24 * HOUR; if (ts > 0) { rest2.d = ts >= SECOND ? Math.floor(ts / DAY) : 0; rest2.h = Math.floor(ts % DAY / HOUR); rest2.m = Math.floor(ts % HOUR / MINUTE); rest2.s = Math.floor(ts % MINUTE / SECOND); rest2.ms = Math.floor(ts % SECOND); } return type === "custom" ? rest2 : parseFormat(Object.assign({}, rest2)); }; const parseFormat = (time2) => { const { d } = time2; let { h, m, s, ms } = time2; let formatCache = format; if (formatCache.includes("DD")) { formatCache = formatCache.replace("DD", padZero(d)); } else { h += Number(d) * 24; } if (formatCache.includes("HH")) { formatCache = formatCache.replace("HH", padZero(h)); } else { m += Number(h) * 60; } if (formatCache.includes("mm")) { formatCache = formatCache.replace("mm", padZero(m)); } else { s += Number(m) * 60; } if (formatCache.includes("ss")) { formatCache = formatCache.replace("ss", padZero(s)); } else { ms += Number(s) * 1e3; } if (formatCache.includes("S")) { const msC = padZero(ms, 3).toString(); if (formatCache.includes("SSS")) { formatCache = formatCache.replace("SSS", msC); } else if (formatCache.includes("SS")) { formatCache = formatCache.replace("SS", msC.slice(0, 2)); } else if (formatCache.includes("S")) { formatCache = formatCache.replace("SS", msC.slice(0, 1)); } } return formatCache; }; const pause = () => { cancelAnimationFrame(stateRef.current.timer); stateRef.current.counting = false; onPaused && onPaused(stateRef.current.restTime); }; useImperativeHandle(ref, () => ({ start: () => { if (!stateRef.current.counting && !autoStart) { stateRef.current.counting = true; stateRef.current.handleEndTime = Date.now() + Number(stateRef.current.restTime); tick(); onRestart && onRestart(stateRef.current.restTime); } }, pause: () => { cancelAnimationFrame(stateRef.current.timer); stateRef.current.counting = false; onPaused && onPaused(stateRef.current.restTime); }, reset: () => { if (!autoStart) { pause(); stateRef.current.restTime = time; setRestTime(time); } } })); useEffect(() => { const tranTime = formatRemainTime(stateRef.current.restTime, "custom"); onUpdate && onUpdate(tranTime); }, [restTimeStamp]); useEffect(() => { if (stateRef.current.isIninted) { if (paused) { if (stateRef.current.counting) { pause(); } } else { if (!stateRef.current.counting) { stateRef.current.counting = true; stateRef.current.handleEndTime = Date.now() + Number(stateRef.current.restTime); tick(); } onRestart && onRestart(stateRef.current.restTime); } } }, [paused]); useEffect(() => { if (stateRef.current.isIninted) { initTime(); } }, [endTime, startTime, remainingTime]); useEffect(() => { if (autoStart) { initTime(); } else { stateRef.current.restTime = time; setRestTime(time); } if (!stateRef.current.isIninted) { stateRef.current.isIninted = true; } return componentWillUnmount; }, []); const componentWillUnmount = () => { destroy && cancelAnimationFrame(stateRef.current.timer); }; const renderTime = (() => { return formatRemainTime(stateRef.current.restTime); })(); return React__default.createElement("div", Object.assign({ className: `${classPrefix} ${className}`, style: Object.assign({}, style) }, rest), children || React__default.createElement("div", { className: `${classPrefix}-block`, // eslint-disable-next-line react/no-danger dangerouslySetInnerHTML: { __html: `${renderTime}` } })); }; const CountDown = React__default.forwardRef(InternalCountDown); CountDown.displayName = "NutCountDown"; export { CountDown as default };