Created
February 24, 2022 05:15
-
-
Save Mitscherlich/653f412edde0a1f000413455b55312b0 to your computer and use it in GitHub Desktop.
Simple Timer component (Vue 2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import Vue, { VNode } from 'vue' | |
import isNumber from 'lodash/isNumber' | |
import PropTypes from 'vue-types' | |
const EVENT_VISIBILITY_CHANGE = 'visibilitychange' | |
const PropTypeNatural = PropTypes.custom((val: number) => isNumber(val) && val >= 0) | |
export const TimerProps = { | |
interval: PropTypeNatural.def(1000), | |
silent: PropTypes.bool.def(false), | |
autoStart: PropTypes.bool.def(true), | |
} | |
export const Timer = Vue.extend({ | |
name: 'Timer', | |
props: TimerProps, | |
data() { | |
const now = new Date() | |
return { | |
hours: now.getHours(), | |
minutes: now.getMinutes(), | |
seconds: now.getSeconds(), | |
ticking: false, | |
timerId: null as unknown as number, | |
} | |
}, | |
created() { | |
if (this.autoStart) { | |
this.start() | |
} | |
document.addEventListener(EVENT_VISIBILITY_CHANGE, this.handleVisibilityChange) | |
}, | |
beforeDestroy() { | |
document.removeEventListener(EVENT_VISIBILITY_CHANGE, this.handleVisibilityChange) | |
this.pause() | |
}, | |
methods: { | |
start() { | |
if (this.ticking) { | |
return | |
} | |
this.ticking = true | |
if (!this.silent) { | |
this.$emit('start') | |
} | |
if (document.visibilityState === 'visible') { | |
this.continue() | |
} | |
}, | |
stop() { | |
this.pause() | |
this.ticking = false | |
this.$emit('stop') | |
}, | |
pause() { | |
if ('requestAnimationFrame' in window) { | |
cancelAnimationFrame(this.timerId) | |
} else { | |
clearTimeout(this.timerId) | |
} | |
this.$emit('pause') | |
}, | |
resume() { | |
if (this.ticking) { | |
return | |
} | |
this.continue() | |
this.$emit('resume') | |
}, | |
continue() { | |
if (!this.ticking) { | |
return | |
} | |
const now = new Date() | |
const delay = Math.min(this.interval, this.interval - now.getMilliseconds()) | |
if ('requestAnimationFrame' in window) { | |
let start: number | |
const step = (timestamp: number) => { | |
if (!start) { | |
start = timestamp | |
} | |
if (timestamp - start < delay) { | |
this.timerId = requestAnimationFrame(step) | |
} else { | |
this.progress() | |
} | |
} | |
this.timerId = requestAnimationFrame(step) | |
} else { | |
this.timerId = setTimeout(() => { | |
this.progress() | |
}, delay, []) | |
} | |
}, | |
progress() { | |
if (!this.ticking) { | |
return | |
} | |
this.update() | |
if (!this.silent) { | |
this.$emit('tick', { | |
hours: this.hours, | |
minutes: this.minutes, | |
seconds: this.seconds, | |
}) | |
} | |
this.continue() | |
}, | |
update() { | |
const now = new Date() | |
this.hours = now.getHours() | |
this.minutes = now.getMinutes() | |
this.seconds = now.getSeconds() | |
}, | |
handleVisibilityChange() { | |
switch (document.visibilityState) { | |
case 'visible': | |
this.update() | |
this.continue() | |
break | |
case 'hidden': | |
this.pause() | |
break | |
default: | |
break | |
} | |
}, | |
}, | |
render(): VNode { | |
return this.$scopedSlots.default?.({ | |
hours: this.hours, | |
minutes: this.minutes, | |
seconds: this.seconds, | |
}) as unknown as VNode | |
}, | |
}) | |
export interface TimerOptions { | |
interval: number | |
silent: boolean | |
autoStart: boolean | |
} | |
export default class TimerFactory { | |
private options: TimerOptions | |
constructor(options: Partial<TimerOptions> = {}) { | |
const { interval = 1000, silent = false, autoStart = true } = options | |
this.options = { | |
interval, | |
silent, | |
autoStart, | |
} | |
Object.setPrototypeOf(this, this.create()) | |
} | |
create(options = this.options) { | |
return new Timer({ | |
propsData: options, | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment