diff options
| author | Acid Chicken (硫酸鶏) <root@acid-chicken.com> | 2023-04-29 22:57:46 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-04-29 22:57:46 +0900 |
| commit | 9d5911d4e436859127f38d5fcdaa6cb5ce6d01e6 (patch) | |
| tree | 4feca3f9e8ed46acc0ee3abd7ad357c8d1f05d8e | |
| parent | fix(backend): `alsoKnownAs`の誤った定義を修正 (#10725) (diff) | |
| download | sharkey-9d5911d4e436859127f38d5fcdaa6cb5ce6d01e6.tar.gz sharkey-9d5911d4e436859127f38d5fcdaa6cb5ce6d01e6.tar.bz2 sharkey-9d5911d4e436859127f38d5fcdaa6cb5ce6d01e6.zip | |
feat: make `MkImgWithBlurhash` transitionable (#10500)
* feat: make `MkImgWithBlurhash` animatable
* refactor: split out transition styles
* fix: bug
* test: waitFor image loads
* style: remove unused await
* fix
* fix type error
---------
Co-authored-by: tamaina <tamaina@hotmail.co.jp>
3 files changed, 88 insertions, 24 deletions
diff --git a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts index e46a708192..57b3e75513 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts +++ b/packages/frontend/src/components/MkGalleryPostPreview.stories.impl.ts @@ -28,9 +28,11 @@ export const Default = { async play({ canvasElement }) { const canvas = within(canvasElement); const links = canvas.getAllByRole('link'); - await expect(links).toHaveLength(2); - await expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`); - await expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`); + expect(links).toHaveLength(2); + expect(links[0]).toHaveAttribute('href', `/gallery/${galleryPost().id}`); + expect(links[1]).toHaveAttribute('href', `/@${galleryPost().user.username}@${galleryPost().user.host}`); + const images = canvas.getAllByRole<HTMLImageElement>('img'); + await waitFor(() => expect(Promise.all(images.map((image) => image.decode()))).resolves.toBeDefined()); }, args: { post: galleryPost(), diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 944f5ad97b..4f8f7b945a 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -1,9 +1,21 @@ <template> <MkA :to="`/gallery/${post.id}`" class="ttasepnz _panel" tabindex="-1" @pointerenter="enterHover" @pointerleave="leaveHover"> <div class="thumbnail"> - <ImgWithBlurhash class="img" :hash="post.files[0].blurhash"/> <Transition> - <ImgWithBlurhash v-if="show" class="img layered" :src="post.files[0].thumbnailUrl" :hash="post.files[0].blurhash"/> + <ImgWithBlurhash + class="img layered" + :transition="safe ? null : { + enterActiveClass: $style.transition_toggle_enterActive, + leaveActiveClass: $style.transition_toggle_leaveActive, + enterFromClass: $style.transition_toggle_enterFrom, + leaveToClass: $style.transition_toggle_leaveTo, + enterToClass: $style.transition_toggle_enterTo, + leaveFromClass: $style.transition_toggle_leaveFrom, + }" + :src="post.files[0].thumbnailUrl" + :hash="post.files[0].blurhash" + :force-blurhash="!show" + /> </Transition> </div> <article> @@ -28,7 +40,8 @@ const props = defineProps<{ }>(); const hover = ref(false); -const show = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive || hover.value); +const safe = computed(() => defaultStore.state.nsfw === 'ignore' || defaultStore.state.nsfw === 'respect' && !props.post.isSensitive); +const show = computed(() => safe.value || hover.value); function enterHover(): void { hover.value = true; @@ -39,6 +52,27 @@ function leaveHover(): void { } </script> +<style lang="scss" module> +.transition_toggle_enterActive, +.transition_toggle_leaveActive { + transition: opacity 0.5s; + position: absolute; + top: 0; + left: 0; +} + +.transition_toggle_enterFrom, +.transition_toggle_leaveTo { + opacity: 0; +} + +.transition_toggle_enterTo, +.transition_toggle_leaveFrom { + transition: none; + opacity: 1; +} +</style> + <style lang="scss" scoped> .ttasepnz { display: block; @@ -66,7 +100,7 @@ function leaveHover(): void { width: 100%; height: 100%; position: absolute; - transition: all 0.5s ease; + transition: transform 0.5s ease; > .img { width: 100%; @@ -76,16 +110,6 @@ function leaveHover(): void { &.layered { position: absolute; top: 0; - - &.v-enter-active, - &.v-leave-active { - transition: opacity 0.5s ease; - } - - &.v-enter-from, - &.v-leave-to { - opacity: 0; - } } } } diff --git a/packages/frontend/src/components/MkImgWithBlurhash.vue b/packages/frontend/src/components/MkImgWithBlurhash.vue index 048dcc7045..2bdb059614 100644 --- a/packages/frontend/src/components/MkImgWithBlurhash.vue +++ b/packages/frontend/src/components/MkImgWithBlurhash.vue @@ -1,15 +1,37 @@ <template> <div :class="[$style.root, { [$style.cover]: cover }]" :title="title ?? ''"> - <canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title ?? ''"/> - <img v-if="src && !forceBlurhash" v-show="loaded" :class="$style.img" :src="src" :title="title ?? ''" :alt="alt ?? ''" @load="onLoad"/> + <img v-if="!loaded && src && !forceBlurhash" :class="$style.loader" :src="src" @load="onLoad"/> + <Transition + mode="in-out" + :enter-active-class="defaultStore.state.animation && (props.transition?.enterActiveClass ?? $style['transition_toggle_enterActive']) || undefined" + :leave-active-class="defaultStore.state.animation && (props.transition?.leaveActiveClass ?? $style['transition_toggle_leaveActive']) || undefined" + :enter-from-class="defaultStore.state.animation && props.transition?.enterFromClass || undefined" + :leave-to-class="defaultStore.state.animation && props.transition?.leaveToClass || undefined" + :enter-to-class="defaultStore.state.animation && props.transition?.enterToClass || undefined" + :leave-from-class="defaultStore.state.animation && props.transition?.leaveFromClass || undefined" + > + <canvas v-if="!loaded || forceBlurhash" ref="canvas" :class="$style.canvas" :width="width" :height="height" :title="title"/> + <img v-else :class="$style.img" :src="src ?? undefined" :title="title ?? undefined" :alt="alt ?? undefined"/> + </Transition> </div> </template> <script lang="ts" setup> -import { onMounted, watch } from 'vue'; +import { onMounted, shallowRef, useCssModule, watch } from 'vue'; import { decode } from 'blurhash'; +import { defaultStore } from '@/store'; + +const $style = useCssModule(); const props = withDefaults(defineProps<{ + transition?: { + enterActiveClass?: string; + leaveActiveClass?: string; + enterFromClass?: string; + leaveToClass?: string; + enterToClass?: string; + leaveFromClass?: string; + } | null; src?: string | null; hash?: string; alt?: string | null; @@ -19,6 +41,7 @@ const props = withDefaults(defineProps<{ cover?: boolean; forceBlurhash?: boolean; }>(), { + transition: null, src: null, alt: '', title: null, @@ -28,7 +51,7 @@ const props = withDefaults(defineProps<{ forceBlurhash: false, }); -const canvas = $shallowRef<HTMLCanvasElement>(); +const canvas = shallowRef<HTMLCanvasElement>(); let loaded = $ref(false); let width = $ref(props.width); let height = $ref(props.height); @@ -51,15 +74,15 @@ watch([() => props.width, () => props.height], () => { }); function draw() { - if (props.hash == null) return; + if (props.hash == null || !canvas.value) return; const pixels = decode(props.hash, width, height); - const ctx = canvas.getContext('2d'); + const ctx = canvas.value.getContext('2d'); const imageData = ctx!.createImageData(width, height); imageData.data.set(pixels); ctx!.putImageData(imageData, 0, 0); } -watch(() => props.hash, () => { +watch([() => props.hash, canvas], () => { draw(); }); @@ -69,6 +92,21 @@ onMounted(() => { </script> <style lang="scss" module> +.transition_toggle_enterActive, +.transition_toggle_leaveActive { + position: absolute; + top: 0; + left: 0; +} + +.loader { + position: absolute; + top: 0; + left: 0; + width: 0; + height: 0; +} + .root { position: relative; width: 100%; |