feat: impl IdleRender
This commit is contained in:
parent
2b19e1f732
commit
b346b99527
|
@ -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'),
|
||||
])
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
41
packages/frontend/src/scripts/idle-render.ts
Normal file
41
packages/frontend/src/scripts/idle-render.ts
Normal 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();
|
Loading…
Reference in a new issue