倒计时的方式五花八门,寻找一个合适的也挺麻烦的,这里我收集了一些覆盖比较广的实现方式,代码仅供参考。
方式1
推荐的方式
这种,根据一种相差值动态去推算其他
返回根据天为最高单位的结果
const diffTime = (time: number | Date) => {
try {
const diffM = dayjs(time).diff(new Date(), 'minute')
const D = diffM / 60 / 24
const H = (D - parseInt(D.toString())) * 24
const m = (H - parseInt(H.toString())) * 60
return `${parseInt(D.toString())}d,${parseInt(H.toString())}h,${parseInt(m.toString())}m`
} catch (error) {
return '0d,0h,0m'
}
}
OR
const diffTime = (time: number | Date) => {
try {
const diffM = differenceInSeconds(time, new Date()) / 60;
const D = diffM / 60 / 24;
const H = (D - parseInt(D.toString())) * 24;
const m = (H - parseInt(H.toString())) * 60;
return `${parseInt(D.toString())}d,${parseInt(H.toString())}h,${parseInt(
m.toString(),
)}m`;
} catch (error) {
return '0d,0h,0m';
}
};
如果是小时为顶峰
// const toString = (n: number) => n > 10 ? parseInt(n.toString()) : '0' + parseInt(n.toString());
const toString = (n: number) => (n > 10 ? n.toFixed(0) : '0' + n.toFixed(0));
const diffTime = (time: number | Date) => {
try {
const diffM = differenceInSeconds(time, new Date()) / 60;
const H = diffM / 60;
const m = (H - parseInt(H.toString())) * 60;
const s = (m - parseInt(m.toString())) * 60;
return `${toString(H)}h,${toString(m)}m,${toString(s)}s`;
} catch (error) {
return '0h,0m,0s';
}
};
请注意,用秒为单位的时候,我把tostring写了两个,然后注释了一个。这是因为,上面有一个bug是,定时器有稍微浮动,比如上一秒结果是2.9999999, 下一秒是2.00000000001, 这时候会在2秒的时候停留两次。所以使用四舍五入的方法解决这个问题。
方式2
也有这种
根据一种相差值,反复推算其他值,然后直接格式一定的时间格式
这种和第一种差不多,不过代码要长很多。(这是网络copy的,不是本人写的, 甩锅成功~~)
还有这种
不过其实都挺一般,代码又长性能又差。
方式3
这种是一种相对成熟的计算方式,考虑到了性能和格式等问题
下面一个是性能比较好的一个介绍,也是我比较早之前写的,不过今天重点不是这个,先看下代码。 原文链接 倒计时
const Fresh = (callBacks = []) => {
let len
const _fresh = (endtime, cb, setReVal, dateIndex) => {
// start end date
const date = +new Date()
, leftsecond = ((endtime - date) / 1000) | 0
// funcs
,isStop = Boolean(leftsecond > 0)
, result = state => val => ({state, val})
// vars
let dataObj = {d: void 0, h: void 0, m: void 0,s: void 0,}
,re = void 0
// default result val format
if(!setReVal)
setReVal = ({d, h, m, s}) => `${d}天${h}小时${m}分${s}秒`
// computed date
dataObj = {
d: parseInt(leftsecond / 3600 / 24),
h: parseInt((leftsecond / 3600) % 24),
m: parseInt((leftsecond / 60) % 60),
s: parseInt(leftsecond % 60),
}
// set result
re = isStop
? result(true)
(setReVal(dataObj))
: result(false)
("结束")
// stop
isStop || callBacks.splice(dateIndex, 1)
// cb
cb(re)
}
// export
const init = (endDateVal, cb, setReVal) => {
callBacks.push([endDateVal, cb, setReVal])
}
// export
const start = () => {
const palyInterval = () => {
if(callBacks.length == 0){
clearInterval(sh)
return
}
callBacks.forEach((params, index) => {
len = len ? len : params.length
params[len] = index
_fresh(...params)
})
}
palyInterval()
// definition Interval
const sh = setInterval(palyInterval, 1000)
}
return {
add: init,
start
}
}
export default Fresh
这个代码考虑的比较多,但是用起来比较麻烦,就是对于时间的处理逻辑
这里是多任务合并到一个定时器里面, 在不同的时间段执行不同的处理,红色的代表时间轴 绿色的代表当前时间 黑色的表示一个倒计时进程
方式4
我感觉上面写的都不怎么样,要么太长了,要么太麻烦了,要么不利于阅读。
const downTimer: (op: { date1: number; date2?: number }) => string = ({
date1,
date2 = Date.now(),
}) => {
const date = new Date(Math.abs(date1 - date2));
const Y = date.getFullYear() - 1970;
const M = date.getMonth();
const D = date.getDate();
const h = date.getHours();
const m = date.getMinutes();
const s = date.getSeconds();
return `${Y}Y ${M}M ${D}D ${h}h ${m}m ${s}s`;
};
这种方式就很酷,但是有限制,格式固定到了年,而第一种方式是固定到了天,更符合一般业务。
注: 思路由Air提供
数据的更新
除了方式三外,其他的都没有自动更新功能,动态更新数据的方式
const CardPutSaleTimer: React.FC<{
openAt: Date;
}> = memo(({ openAt }) => {
const classes = useStyles();
const [timeValue, setTime] = useState('');
const n = useCount();
useEffect(() => {
setTime(diffTime(openAt));
}, [openAt, n]);
return (
<div className={classes.container}>
<div className={classes.time}>{timeValue}</div>
</div>
);
});
useCount是一个动态计算的值, 可以充当刷新功能
const DELAY = 3000;
export const useCount = (delay = DELAY) => {
const [count, setCount] = useState(0);
useInterval(() => {
setCount(currentCount => currentCount + 1);
}, delay);
return count;
};
组件例子
import { Box, makeStyles, Theme } from '@material-ui/core';
import differenceInSeconds from 'date-fns/differenceInSeconds';
import { useCount } from 'modules/common/hooks/useTimer';
import { t } from 'modules/i18n/utils/intl';
import { useEffect } from 'react';
import { useState } from 'react';
import { memo } from 'react';
const useStyles = makeStyles<Theme>(theme => ({
container: {
position: 'absolute',
left: 0,
top: 0,
height: '100%',
width: '100%',
background: 'rgba(0,0,0,0.5)',
color: '#fff',
},
tips: {
fontSize: 14,
opacity: 0.4,
marginBottom: 7,
},
time: {
fontSize: 18,
},
}));
const toString = (n: number) => (n > 10 ? n.toFixed(0) : '0' + n.toFixed(0));
const diffTime = (time: number | Date) => {
try {
const diffM = differenceInSeconds(time, new Date()) / 60;
const H = diffM / 60;
const m = (H - parseInt(H.toString())) * 60;
const s = (m - parseInt(m.toString())) * 60;
return `${toString(H)}h ${toString(m)}m ${toString(s)}s`;
} catch (error) {
return '0h 0m 0s';
}
};
const CardPutSaleTimer: React.FC<{
openAt: Date;
}> = memo(({ openAt }) => {
const classes = useStyles();
const [timeValue, setTime] = useState('');
const reload = useCount(1e3);
const [isTimeOver, setIsTimeOver] = useState(false);
useEffect(() => {
if (+openAt < Date.now()) {
setIsTimeOver(true);
return () => {};
}
setTime(diffTime(openAt));
}, [openAt, reload]);
return isTimeOver ? (
<></>
) : (
<Box
display="flex"
justifyContent="center"
alignItems="center"
className={classes.container}
>
<div>
<div className={classes.tips}>{t('product-card.pool-start-tips')}</div>
<div className={classes.time}>{timeValue}</div>
</div>
</Box>
);
});
export default CardPutSaleTimer;
效果预览
除此之外,还可以在hook函数里面内置刷新,如
import { useInterval } from 'modules/common/hooks/useInterval';
import { Milliseconds, Minutes } from 'modules/common/types/unit';
import { getTimeRemaining } from 'modules/common/utils/getTimeRemaining';
import { t } from 'modules/i18n/utils/intl';
import { useMemo, useState } from 'react';
const INTERVAL_BREAKPOINT: Minutes = 2; // minutes
const SHORT_STEP: Milliseconds = 1000; // when time left less than INTERVAL_BREAKPOINT
const NORMAL_STEP: Milliseconds = 1000 * 30;
export const useTimer = (endDate: Date) => {
const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(endDate));
const delayInterval = useMemo(() => {
if (timeRemaining.minutes >= INTERVAL_BREAKPOINT) {
return NORMAL_STEP;
}
if (timeRemaining.total > 0) {
return SHORT_STEP;
}
return null;
}, [timeRemaining.minutes, timeRemaining.total]);
useInterval(() => {
if (!endDate) return;
setTimeRemaining(getTimeRemaining(endDate));
}, delayInterval);
const isTimeOver = timeRemaining.total <= 0;
const duration = t('time.time-left-short', {
days:
timeRemaining.days !== 0 &&
t('time.days-short', {
days: timeRemaining.days,
}),
hours:
timeRemaining.hours !== 0 &&
t('time.hours-short', {
hours: timeRemaining.hours,
}),
minutes:
timeRemaining.minutes !== 0 &&
t('time.minutes-short', {
minutes: timeRemaining.minutes,
}),
seconds:
timeRemaining.minutes < INTERVAL_BREAKPOINT &&
timeRemaining.seconds !== 0 &&
t('time.seconds-short', {
seconds: timeRemaining.seconds,
}),
});
const modificationDate = (value: number): string => {
return value < 10 ? `0${value}` : `${value}`;
};
// 'Ended on 01/07/2021'
const endDetailedDate = t('time.time-end', {
days: modificationDate(endDate.getDate()),
months: modificationDate(endDate.getMonth() + 1),
years: endDate.getFullYear(),
});
return { timeRemaining, duration, isTimeOver, endDetailedDate };
};
不过我觉得除了方式三必须内置外,其他不内置其实才是最方便的
另外 也希望大家可以给多语言内部贡献倒计时插件等,这样大家才可以彻底摆脱倒计时手写的麻烦。
-- 完--