feat: impl IdleRender

This commit is contained in:
Acid Chicken (硫酸鶏) 2023-04-09 14:08:58 +09:00
parent 2b19e1f732
commit b346b99527
No known key found for this signature in database
GPG key ID: 3E87B98A3F6BAB99
6 changed files with 89 additions and 34 deletions

View file

@ -397,6 +397,8 @@ function toStories(component: string): string {
// glob('src/{components,pages,ui,widgets}/**/*.vue')
Promise.all([
glob('src/components/global/*.vue'),
glob('src/components/MkAnalogClock.vue'),
glob('src/components/MkDigitalClock.vue'),
glob('src/components/MkGalleryPostPreview.vue'),
glob('src/pages/user/home.vue'),
])

View file

@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/explicit-function-return-type */
import { StoryObj } from '@storybook/vue3';
import isChromatic from 'chromatic';
import MkAnalogClock from './MkAnalogClock.vue';
export const Default = {
render(args) {
@ -22,6 +23,14 @@ export const Default = {
template: '<MkAnalogClock v-bind="props" />',
};
},
args: {
now: isChromatic() ? () => new Date('2023-01-01T10:10:30') : undefined,
},
decorators: [
() => ({
template: '<div style="container-type:inline-size;height:100%"><div style="height:100cqmin;margin:auto;width:100cqmin"><story/></div></div>',
}),
],
parameters: {
layout: 'fullscreen',
},

View file

@ -39,6 +39,7 @@
-->
<line
ref="sLine"
:class="[$style.s, { [$style.animate]: !disableSAnimate && sAnimation !== 'none', [$style.elastic]: sAnimation === 'elastic', [$style.easeOut]: sAnimation === 'easeOut' }]"
:x1="5 - (0 * (sHandLengthRatio * handsTailLength))"
:y1="5 + (1 * (sHandLengthRatio * handsTailLength))"
@ -73,9 +74,10 @@
</template>
<script lang="ts" setup>
import { computed, onMounted, onBeforeUnmount } from 'vue';
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
import tinycolor from 'tinycolor2';
import { globalEvents } from '@/events.js';
import { defaultIdleRender } from '@/scripts/idle-render.js';
// https://stackoverflow.com/questions/1878907/how-can-i-find-the-difference-between-two-angles
const angleDiff = (a: number, b: number) => {
@ -99,6 +101,7 @@ const props = withDefaults(defineProps<{
graduations?: 'none' | 'dots' | 'numbers';
fadeGraduations?: boolean;
sAnimation?: 'none' | 'elastic' | 'easeOut';
now?: () => Date;
}>(), {
numbers: false,
thickness: 0.1,
@ -107,6 +110,7 @@ const props = withDefaults(defineProps<{
graduations: 'dots',
fadeGraduations: true,
sAnimation: 'elastic',
now: () => new Date(),
});
const graduationsMajor = computed(() => {
@ -143,26 +147,37 @@ let mAngle = $ref<number>(0);
let sAngle = $ref<number>(0);
let disableSAnimate = $ref(false);
let sOneRound = false;
const sLine = ref<SVGPathElement>();
function tick() {
const now = new Date();
now.setMinutes(now.getMinutes() + (new Date().getTimezoneOffset() + props.offset));
const now = props.now();
now.setMinutes(now.getMinutes() + now.getTimezoneOffset() + props.offset);
const previousS = s;
const previousM = m;
const previousH = h;
s = now.getSeconds();
m = now.getMinutes();
h = now.getHours();
if (previousS === s && previousM === m && previousH === h) {
return;
}
hAngle = Math.PI * (h % (props.twentyfour ? 24 : 12) + (m + s / 60) / 60) / (props.twentyfour ? 12 : 6);
mAngle = Math.PI * (m + s / 60) / 30;
if (sOneRound) { // (59->0)
if (sOneRound && sLine.value) { // (59->0)
sAngle = Math.PI * 60 / 30;
window.setTimeout(() => {
defaultIdleRender.delete(tick);
sLine.value.addEventListener('transitionend', () => {
disableSAnimate = true;
window.setTimeout(() => {
requestAnimationFrame(() => {
sAngle = 0;
window.setTimeout(() => {
requestAnimationFrame(() => {
disableSAnimate = false;
}, 100);
}, 100);
}, 700);
if (enabled) {
defaultIdleRender.add(tick);
}
});
});
}, { once: true });
} else {
sAngle = Math.PI * s / 30;
}
@ -186,20 +201,13 @@ function calcColors() {
calcColors();
onMounted(() => {
const update = () => {
if (enabled) {
tick();
window.setTimeout(update, 1000);
}
};
update();
defaultIdleRender.add(tick);
globalEvents.on('themeChanged', calcColors);
});
onBeforeUnmount(() => {
enabled = false;
defaultIdleRender.delete(tick);
globalEvents.off('themeChanged', calcColors);
});
</script>

View file

@ -11,7 +11,8 @@
</template>
<script lang="ts" setup>
import { onUnmounted, ref, watch } from 'vue';
import { onMounted, onUnmounted, ref, watch } from 'vue';
import { defaultIdleRender } from '@/scripts/idle-render.js';
const props = withDefaults(defineProps<{
showS?: boolean;
@ -23,7 +24,6 @@ const props = withDefaults(defineProps<{
offset: 0 - new Date().getTimezoneOffset(),
});
let intervalId;
const hh = ref('');
const mm = ref('');
const ss = ref('');
@ -52,13 +52,12 @@ const tick = () => {
tick();
watch(() => props.showMs, () => {
if (intervalId) window.clearInterval(intervalId);
intervalId = window.setInterval(tick, props.showMs ? 10 : 1000);
}, { immediate: true });
onMounted(() => {
defaultIdleRender.add(tick);
});
onUnmounted(() => {
window.clearInterval(intervalId);
defaultIdleRender.remove(tick);
});
</script>

View file

@ -11,6 +11,7 @@
import { onUnmounted } from 'vue';
import { i18n } from '@/i18n';
import { dateTimeFormat } from '@/scripts/intl-const';
import { defaultIdleRender } from '@/scripts/idle-render.js';
const props = withDefaults(defineProps<{
time: Date | string | number | null;
@ -45,21 +46,16 @@ const relative = $computed<string>(() => {
i18n.ts._ago.future);
});
let tickId: number;
function tick() {
function tick(): void {
now = props.origin ?? (new Date()).getTime();
const ago = (now - _time) / 1000/*ms*/;
const next = ago < 60 ? 10000 : ago < 3600 ? 60000 : 180000;
tickId = window.setTimeout(tick, next);
}
if (props.mode === 'relative' || props.mode === 'detail') {
tick();
defaultIdleRender.add(tick);
onUnmounted(() => {
window.clearTimeout(tickId);
defaultIdleRender.delete(tick);
});
}
</script>

View file

@ -0,0 +1,41 @@
// eslint-disable-next-line import/no-default-export
export default class IdleRender {
#renderers: Set<FrameRequestCallback>;
#budget: number;
#rafId: number;
#ricId: number;
constructor(budget = 0) {
this.#renderers = new Set();
this.#budget = budget;
this.#rafId = 0;
this.#ricId = requestIdleCallback((deadline) => this.#render(deadline));
}
#render(deadline: IdleDeadline): void {
if (deadline.timeRemaining() > this.#budget) {
this.#rafId = requestAnimationFrame((time) => {
for (const renderer of this.#renderers) {
renderer(time);
}
});
}
this.#ricId = requestIdleCallback((arg) => this.#render(arg));
}
add(renderer: FrameRequestCallback): void {
this.#renderers.add(renderer);
}
delete(renderer: FrameRequestCallback): void {
this.#renderers.delete(renderer);
}
dispose(): void {
this.#renderers.clear();
cancelAnimationFrame(this.#rafId);
cancelIdleCallback(this.#ricId);
}
}
export const defaultIdleRender = new IdleRender();