enhance(frontend): テーマ切り替えのアニメーションをView Transitionに変更 (#15974)
* enhance(frontend): テーマ切り替えのアニメーションをView Transitionに変更 * fix lint * fix: 切り替え時間を0.5sに
This commit is contained in:
parent
bd7633c70e
commit
2619f69238
|
@ -31,9 +31,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { onMounted, ref, useTemplateRef, watch } from 'vue';
|
import { onBeforeUnmount, onMounted, ref, useTemplateRef, watch } from 'vue';
|
||||||
import { miLocalStorage } from '@/local-storage.js';
|
import { miLocalStorage } from '@/local-storage.js';
|
||||||
import { prefer } from '@/preferences.js';
|
import { prefer } from '@/preferences.js';
|
||||||
|
import { globalEvents } from '@/events.js';
|
||||||
import { getBgColor } from '@/utility/get-bg-color.js';
|
import { getBgColor } from '@/utility/get-bg-color.js';
|
||||||
|
|
||||||
const miLocalStoragePrefix = 'ui:folder:' as const;
|
const miLocalStoragePrefix = 'ui:folder:' as const;
|
||||||
|
@ -83,8 +84,19 @@ function afterLeave(el: Element) {
|
||||||
el.style.height = '';
|
el.style.height = '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function updateBgColor() {
|
||||||
|
if (rootEl.value) {
|
||||||
|
parentBg.value = getBgColor(rootEl.value.parentElement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
parentBg.value = getBgColor(rootEl.value?.parentElement);
|
updateBgColor();
|
||||||
|
globalEvents.on('themeChanging', updateBgColor);
|
||||||
|
});
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {
|
||||||
|
globalEvents.off('themeChanging', updateBgColor);
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -90,12 +90,49 @@ html {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
html._themeChanging_ {
|
html._themeChangingFallback_ {
|
||||||
&, * {
|
&, * {
|
||||||
transition: background 1s ease, border 1s ease !important;
|
transition: background 0.5s ease, border 0.5s ease !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
html._themeChanging_ {
|
||||||
|
view-transition-name: theme-changing;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::view-transition-new(theme-changing) {
|
||||||
|
z-index: 4000001;
|
||||||
|
animation: themeChangingNew 0.5s ease;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
html::view-transition-old(theme-changing) {
|
||||||
|
z-index: 4000000;
|
||||||
|
animation: themeChangingOld 0.5s ease;
|
||||||
|
animation-fill-mode: forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes themeChangingNew {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes themeChangingOld {
|
||||||
|
0% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
html,
|
html,
|
||||||
body,
|
body,
|
||||||
#misskey_app {
|
#misskey_app {
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ref } from 'vue';
|
import { ref, nextTick } from 'vue';
|
||||||
import tinycolor from 'tinycolor2';
|
import tinycolor from 'tinycolor2';
|
||||||
import lightTheme from '@@/themes/_light.json5';
|
import lightTheme from '@@/themes/_light.json5';
|
||||||
import darkTheme from '@@/themes/_dark.json5';
|
import darkTheme from '@@/themes/_dark.json5';
|
||||||
|
@ -88,20 +88,7 @@ export async function removeTheme(theme: Theme): Promise<void> {
|
||||||
prefer.commit('themes', themes);
|
prefer.commit('themes', themes);
|
||||||
}
|
}
|
||||||
|
|
||||||
let timeout: number | null = null;
|
function applyThemeInternal(theme: Theme, persist: boolean) {
|
||||||
|
|
||||||
export function applyTheme(theme: Theme, persist = true) {
|
|
||||||
if (timeout) window.clearTimeout(timeout);
|
|
||||||
|
|
||||||
window.document.documentElement.classList.add('_themeChanging_');
|
|
||||||
|
|
||||||
timeout = window.setTimeout(() => {
|
|
||||||
window.document.documentElement.classList.remove('_themeChanging_');
|
|
||||||
|
|
||||||
// 色計算など再度行えるようにクライアント全体に通知
|
|
||||||
globalEvents.emit('themeChanged');
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
|
const colorScheme = theme.base === 'dark' ? 'dark' : 'light';
|
||||||
|
|
||||||
window.document.documentElement.dataset.colorScheme = colorScheme;
|
window.document.documentElement.dataset.colorScheme = colorScheme;
|
||||||
|
@ -139,6 +126,37 @@ export function applyTheme(theme: Theme, persist = true) {
|
||||||
globalEvents.emit('themeChanging');
|
globalEvents.emit('themeChanging');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let timeout: number | null = null;
|
||||||
|
|
||||||
|
export function applyTheme(theme: Theme, persist = true) {
|
||||||
|
if (timeout) {
|
||||||
|
window.clearTimeout(timeout);
|
||||||
|
timeout = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (window.document.startViewTransition != null && prefer.s.animation) {
|
||||||
|
window.document.documentElement.classList.add('_themeChanging_');
|
||||||
|
window.document.startViewTransition(async () => {
|
||||||
|
applyThemeInternal(theme, persist);
|
||||||
|
await nextTick();
|
||||||
|
}).finished.then(() => {
|
||||||
|
window.document.documentElement.classList.remove('_themeChanging_');
|
||||||
|
// 色計算など再度行えるようにクライアント全体に通知
|
||||||
|
globalEvents.emit('themeChanged');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// TODO: ViewTransition API が主要ブラウザで対応したら消す
|
||||||
|
window.document.documentElement.classList.add('_themeChangingFallback_');
|
||||||
|
timeout = window.setTimeout(() => {
|
||||||
|
window.document.documentElement.classList.remove('_themeChangingFallback_');
|
||||||
|
// 色計算など再度行えるようにクライアント全体に通知
|
||||||
|
globalEvents.emit('themeChanged');
|
||||||
|
}, 500);
|
||||||
|
|
||||||
|
applyThemeInternal(theme, persist);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function compile(theme: Theme): Record<string, string> {
|
export function compile(theme: Theme): Record<string, string> {
|
||||||
function getColor(val: string): tinycolor.Instance {
|
function getColor(val: string): tinycolor.Instance {
|
||||||
if (val[0] === '@') { // ref (prop)
|
if (val[0] === '@') { // ref (prop)
|
||||||
|
|
Loading…
Reference in New Issue