From 4d67126f0ef1843a67dd7865b2d27cd93a0a22c0 Mon Sep 17 00:00:00 2001 From: thofx Date: Tue, 18 Jul 2023 22:47:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/@core/components/ThemeSwitcher.vue | 87 +++++++++++++++++++ src/@layouts/components/VerticalNavLayout.vue | 5 +- src/@layouts/styles/_default-layout.scss | 4 +- src/components/cards/MediaCard.vue | 31 +++++-- src/styles/styles.scss | 34 ++++++++ 5 files changed, 151 insertions(+), 10 deletions(-) diff --git a/src/@core/components/ThemeSwitcher.vue b/src/@core/components/ThemeSwitcher.vue index 91eafb4f..641adec0 100644 --- a/src/@core/components/ThemeSwitcher.vue +++ b/src/@core/components/ThemeSwitcher.vue @@ -26,6 +26,8 @@ function changeTheme() { globalTheme.name.value = nextTheme savedTheme.value = nextTheme localStorage.setItem('theme', nextTheme) + + themeTransition() } // Update icon if theme is changed from other sources @@ -40,6 +42,75 @@ watch( onMounted(() => { globalTheme.name.value = savedTheme.value }) + +function hasScrollbar(el?: Element | null) { + if (!el || el.nodeType !== Node.ELEMENT_NODE) + return false + + const style = window.getComputedStyle(el) + return style.overflowY === 'scroll' || (style.overflowY === 'auto' && el.scrollHeight > el.clientHeight) +} + +function themeTransition() { + const x = performance.now() + for (let i = 0; i++ < 1e7; (i << 9) & ((9 % 9) * 9 + 9)); + const cost = performance.now() - x + if (cost > 10) + return + + const el: HTMLElement = document.querySelector('[data-v-app]')! + const children = el.querySelectorAll('*') as NodeListOf + + children.forEach((el) => { + if (hasScrollbar(el)) { + el.dataset.scrollX = String(el.scrollLeft) + el.dataset.scrollY = String(el.scrollTop) + } + }) + + const copy = el.cloneNode(true) as HTMLElement + copy.classList.add('app-copy') + const rect = el.getBoundingClientRect() + copy.style.top = `${rect.top}px` + copy.style.left = `${rect.left}px` + copy.style.width = `${rect.width}px` + copy.style.height = `${rect.height}px` + + const targetEl = document.activeElement as HTMLElement + const targetRect = targetEl.getBoundingClientRect() + const left = targetRect.left + targetRect.width / 2 + window.scrollX + const top = targetRect.top + targetRect.height / 2 + window.scrollY + el.style.setProperty('--clip-pos', `${left}px ${top}px`) + el.style.removeProperty('--clip-size') + + nextTick(() => { + el.classList.add('app-transition') + requestAnimationFrame(() => { + requestAnimationFrame(() => { + el.style.setProperty('--clip-size', `${Math.hypot(window.innerWidth, window.innerHeight)}px`) + }) + }) + }) + + document.body.append(copy) + ; (copy.querySelectorAll('[data-scroll-x], [data-scroll-y]') as NodeListOf).forEach((el) => { + el.scrollLeft = +el.dataset.scrollX! + el.scrollTop = +el.dataset.scrollY! + }) + + function onTransitionend(e: TransitionEvent) { + if (e.target === e.currentTarget) { + copy.remove() + el.removeEventListener('transitionend', onTransitionend) + el.removeEventListener('transitioncancel', onTransitionend) + el.classList.remove('app-transition') + el.style.removeProperty('--clip-size') + el.style.removeProperty('--clip-pos') + } + } + el.addEventListener('transitionend', onTransitionend) + el.addEventListener('transitioncancel', onTransitionend) +} + + diff --git a/src/@layouts/components/VerticalNavLayout.vue b/src/@layouts/components/VerticalNavLayout.vue index f98b3be7..457f7e72 100644 --- a/src/@layouts/components/VerticalNavLayout.vue +++ b/src/@layouts/components/VerticalNavLayout.vue @@ -1,4 +1,5 @@