前言:看市面上的主题切换简单的用一个switch组件实现深色和浅色的替换(这里也是)。 也有通过input封装成switch组件active-text=☀️ inactive-text=🌙(美观,作者下一步也准备美化 😆)。
为了给切换加上一个友好的交互效果,决定在这强对比的主题切换过度中制作一个赏心悦目的动画效果。以下是个人增添的dom效果及代码:
先看静态效果
深色主题切换:
浅色主题切换:
TopTip.vue 页面代码
<template>
<div class="theme-fixed-wrap">
<section>
<svg-icon :icon-class="desc.icon" />
<em>正在切换{{ desc.txt }}</em>
</section>
<div class="line-wrap">.</div>
</div>
</template>
<script>
export default {
name: 'Tip',
props: {
desc: { type: Object, default: () => null }
}
}
</script>
<style scoped lang="scss">
@import './style';
</style>
stlye.scss 页面代码
.theme-fixed {
&-wrap {
position: fixed;
top: 0;
left: 50%;
min-width: 100px;
padding: 10px;
transform: translate(-50%, -100px) scale(0.65);
border-radius: 10px;
transition: transform 0.3s ease-in-out;
box-shadow: 0 0 30px rgba($color: var(--color-black-rgb), $alpha: 0.16);
overflow: hidden;
z-index: 100000;
&::after {
content: "";
position: absolute;
top: 60%;
left: 60%;
right: 0;
bottom: 0;
width: 120%;
height: 120%;
display: block;
transform: translate(-60%, -60%);
background-color: rgba($color: var(--border-color-extra-light-rgb), $alpha: 0.68);
filter: blur(10px);
z-index: 0;
}
&.fadeIn {
transform: translate(-50%, 100px) scale(1);
}
& > section {
display: flex;
align-items: center;
padding: 10px;
background-color: var(--border-color-lighter);
border-radius: 10px;
position: relative;
z-index: 1;
& > .svg-icon,
& > em {
line-height: 30px;
font: {
weight: normal;
style: normal;
}
}
& > .svg-icon {
text-align: center;
font-size: 18px;
width: 20px;
height: 20px;
}
& > em {
padding-left: 10px;
text-align: left;
font-size: 14px;
color: var(--color-text-primary);
}
}
.line-wrap {
position: relative;
width: 100%;
height: 50px;
font-size: 0;
z-index: 1;
&::before {
content: "";
position: absolute;
padding: 0;
margin: 0;
top: 50%;
left: 0;
transform: translate(0, -50%) scaleY(0.5);
border: none;
width: 100%;
height: 2px;
background-color: rgba($color: var(--color-black-rgb), $alpha: 0.56);
}
&::after {
content: "";
position: absolute;
top: 50%;
left: 0;
display: block;
width: 20px;
height: 20px;
border-radius: 50%;
transform: translate(0, -50%);
animation-name: identifier;
animation-delay: 0.4s;
animation-duration: 1s;
animation-timing-function: cubic-bezier(0, 1);
animation-iteration-count: 1;
animation-fill-mode: forwards;
}
}
}
}
// 定义动画变量
$radioWidth: 20px;
$dep: calc(0%);
$dest: calc(100% - #{$radioWidth});
$light_color: #ffffff;
$dark_color: #0c3250;
$light_shadow: #ffffff;
$dark_shadow: #000000;
$blur: 20px;
// 设置动画脚本
@keyframes identifier_light {
from {
left: $dep;
background-color: $light_color;
box-shadow: 0 0 $blur $light_shadow;
}
to {
left: $dest;
background-color: $dark_color;
box-shadow: none;
}
}
@keyframes identifier_dark {
from {
left: $dep;
background-color: $dark_color;
box-shadow: none;
}
to {
left: $dest;
background-color: $light_color;
box-shadow: 0 0 $blur $light_shadow;
}
}
// 主题适配
:root[theme="dark"] {
.line-wrap::after {
animation-name: identifier_dark;
}
}
:root[theme="light"] {
.line-wrap::after {
animation-name: identifier_light;
}
}
theme.vue 组件使用方法及页面代码
<template>
<el-switch
v-model="theme"
active-text="深色"
inactive-text="浅色"
active-value="dark"
inactive-value="light"
@change="handleChange"
/>
</template>
// Cookies.get
import { getThemeColors } from '@/utils/utils'
// 自定义封装 El Loading 服务
import { openLoading, closeLoading } from '@/utils/ElLoading'
export default {
data() {
return {
theme: ''
}
},
mounted() {
openLoading('主题加载中...')
let themeColors = getThemeColors() || 'dark'
this.theme = themeColors
this.$store.dispatch('theme/setTheme', themeColors).then(() => {
closeLoading()
})
},
methods: {
// 主题切换
async handleChange(val) {
await create(TopTip, {
desc: { ...getThemeConf(val) }
}).remove()
this.$store.dispatch('theme/setTheme', val)
}
}
}
create.js 页面代码
import Vue from 'vue'
// 全局动画延迟基数设定
export const delay = 300
// 创建DOM
export function create(Component, props) {
const vm = new Vue({
render(h) {
// console.log(h(Component, { props }))
return h(Component, { props })
}
}).$mount()
let el = vm.$el
document.body.appendChild(el)
tryCatch(fadeInTimer)
let fadeInTimer = setTimeout(() => {
el.classList.add('fadeIn')
}, delay / 3)
// 移除DOM
const comp = vm.$children[0]
comp.remove = () => {
return new Promise((resolve) => {
tryCatch(timer)
let timer = setTimeout(() => {
el.classList.remove('fadeIn')
tryCatch(destroyTimer)
let destroyTimer = setTimeout(() => {
document.body.removeChild(el)
vm.$destroy()
resolve(true)
}, delay)
}, delay * 6)
})
}
return comp
}
export function tryCatch(timer) {
try {
clearTimeout(timer)
} catch (error) {
console.error('err', error)
}
}
以上是个人实际项目的核心代码,仅供学习参考。欢迎大家指正,谢谢!