diff --git a/.gitignore b/.gitignore
index 9f11a7d..48541ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ coverage
*.tsbuildinfo
.DS_Store
.worktree/
+.worktrees/
diff --git a/src/components/ui/RevealImage.vue b/src/components/ui/RevealImage.vue
index 89ade59..88360c3 100644
--- a/src/components/ui/RevealImage.vue
+++ b/src/components/ui/RevealImage.vue
@@ -1,71 +1,60 @@
-
+
clearTimeout(settleTimer))
@load="reveal"
@error="fail"
/>
-
-
-
+
+
@@ -95,53 +83,27 @@ onBeforeUnmount(() => clearTimeout(settleTimer))
}
.reveal-image > .n-image img {
opacity: 0;
- filter: blur(9px);
- transform: scale(1.025);
- transition:
- opacity 280ms ease,
- filter 620ms cubic-bezier(0.22, 1, 0.36, 1),
- transform 620ms cubic-bezier(0.22, 1, 0.36, 1);
+ transition: opacity 220ms cubic-bezier(0.25, 1, 0.5, 1);
}
.reveal-image.is-loaded > .n-image img {
opacity: 1;
- filter: blur(0);
- transform: scale(1);
}
-.reveal-image-grid {
- @apply absolute inset-0 grid grid-cols-4 grid-rows-4 pointer-events-none;
- gap: 1px;
- background: var(--app-body);
-}
-.reveal-image-cell {
+.reveal-image-placeholder {
+ @apply absolute inset-0 pointer-events-none;
background-color: var(--app-control);
- background-repeat: no-repeat;
- background-size: 400% 400%;
- opacity: 1;
- transform: scale(1.01);
- transition:
- opacity 420ms cubic-bezier(0.4, 0, 0.2, 1),
- transform 520ms cubic-bezier(0.22, 1, 0.36, 1);
- transition-delay: calc(var(--reveal-order) * 18ms);
+ animation: reveal-image-pulse 1.2s cubic-bezier(0.65, 0, 0.35, 1) infinite alternate;
}
-.reveal-image:not(.is-loaded) .reveal-image-cell {
- animation: reveal-image-wait 1.8s ease-in-out infinite alternate;
- animation-delay: calc(var(--reveal-order) * -55ms);
-}
-.reveal-image.is-loaded .reveal-image-cell {
- opacity: 0;
- transform: scale(0.88);
-}
-@keyframes reveal-image-wait {
+@keyframes reveal-image-pulse {
from {
- filter: brightness(0.96);
+ opacity: 0.72;
}
to {
- filter: brightness(1.08);
+ opacity: 1;
}
}
@media (prefers-reduced-motion: reduce) {
.reveal-image > .n-image img,
- .reveal-image-cell {
+ .reveal-image-placeholder {
animation: none;
transition: none;
}
diff --git a/tests/components/ui/AssetImage.test.ts b/tests/components/ui/AssetImage.test.ts
index 3cd1be7..457d958 100644
--- a/tests/components/ui/AssetImage.test.ts
+++ b/tests/components/ui/AssetImage.test.ts
@@ -33,19 +33,15 @@ describe('资源图片展示与原图预览', () => {
expect(wrapper.get('img').element.style.objectFit).toBe('contain')
})
- it('图片加载完成后按网格逐块显现,并在动画结束后移除覆盖层', async () => {
- vi.useFakeTimers()
- vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => {
- callback(0)
- return 1
- })
- wrapper = mount(AssetImage, { props: { src: '/storage/reveal.png', alt: '加载动效图片' } })
- expect(wrapper.findAll('.reveal-image-cell')).toHaveLength(16)
+ it('图片加载前显示轻量骨架占位,加载完成后直接淡入图片并移除占位', async () => {
+ wrapper = mount(AssetImage, { props: { src: '/storage/reveal.png', alt: '加载中的图片' } })
+ expect(wrapper.get('.reveal-image').attributes('aria-busy')).toBe('true')
+ expect(wrapper.find('.reveal-image-placeholder').exists()).toBe(true)
+ expect(wrapper.find('.reveal-image-grid').exists()).toBe(false)
await wrapper.get('img').trigger('load')
expect(wrapper.get('.reveal-image').classes()).toContain('is-loaded')
- vi.advanceTimersByTime(760)
- await wrapper.vm.$nextTick()
- expect(wrapper.find('.reveal-image-grid').exists()).toBe(false)
+ expect(wrapper.get('.reveal-image').attributes('aria-busy')).toBe('false')
+ expect(wrapper.find('.reveal-image-placeholder').exists()).toBe(false)
})
it('cover 仅裁切卡片,点击使用 Naive 原生预览展示同一完整资源,遮罩可关闭', async () => {