Umi4/Max实现主题切换和过渡效果!

Umi4/Max实现主题切换和过渡效果!

在工作中现在其实很常见的就是主题切换,主题切换挺简单的,但是如果让实现主题切换实现一个过渡的效果还是有点难度的。接下来我们就实现umi4中的主题切换和过渡动画效果,这个其实我也是参照的别人的,在其上面做了一些改动,如果有什么问题希望大家可以交流一下。

封装切换主题的组件,避免代码冗余

这里使用到了纯html+css画出的一个主题切换的组件,以下是封装的代码,只是简单的实现,都是加了TS版本的 ,创建component/SetTheme/index.tsx

javascript 复制代码
import { crop, toCanvas } from '@/utils/setTheme';
import { useAntdConfigSetter } from '@umijs/max'; // 引入设置antdConfig的配置
import { theme } from 'antd';
import { memo, useRef, useState } from 'react';
import './index.css';
const Index = () => {
  const [flag, setFlag] = useState<'dark' | 'light'>('dark');
  const setAntdConfig = useAntdConfigSetter();
  const targetRef = useRef<HTMLLabelElement>(null);
  const setTheme = () => {
    let them = flag === 'light' ? 'dark' : 'light';
    setFlag(them as 'dark' | 'light');
    toCanvas(document.getElementById('root') as HTMLDivElement).then(
      (canvas) => {
        document
          .getElementById('root')
          ?.appendChild(canvas as HTMLCanvasElement);
        crop(
          canvas as HTMLCanvasElement,
          targetRef.current as HTMLLabelElement,
          {
            reverse: flag === 'dark',
          },
        ).then((canvas) => {
          //绘制结束后删除canvas
          document
            .getElementById('root')
            ?.removeChild(canvas as HTMLCanvasElement);
        });
        setAntdConfig({
          theme: {
            algorithm: [
              flag === 'dark' ? theme.darkAlgorithm : theme.defaultAlgorithm,
            ],
          },
        });
      },
    );
  };
  return (
    <>
      <label className="switch" ref={targetRef} onChange={setTheme}>
        <span className="sun">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
            <g fill="#ffd43b">
              <circle r="5" cy="12" cx="12"></circle>
              <path d="m21 13h-1a1 1 0 0 1 0-2h1a1 1 0 0 1 0 2zm-17 0h-1a1 1 0 0 1 0-2h1a1 1 0 0 1 0 2zm13.66-5.66a1 1 0 0 1 -.66-.29 1 1 0 0 1 0-1.41l.71-.71a1 1 0 1 1 1.41 1.41l-.71.71a1 1 0 0 1 -.75.29zm-12.02 12.02a1 1 0 0 1 -.71-.29 1 1 0 0 1 0-1.41l.71-.66a1 1 0 0 1 1.41 1.41l-.71.71a1 1 0 0 1 -.7.24zm6.36-14.36a1 1 0 0 1 -1-1v-1a1 1 0 0 1 2 0v1a1 1 0 0 1 -1 1zm0 17a1 1 0 0 1 -1-1v-1a1 1 0 0 1 2 0v1a1 1 0 0 1 -1 1zm-5.66-14.66a1 1 0 0 1 -.7-.29l-.71-.71a1 1 0 0 1 1.41-1.41l.71.71a1 1 0 0 1 0 1.41 1 1 0 0 1 -.71.29zm12.02 12.02a1 1 0 0 1 -.7-.29l-.66-.71a1 1 0 0 1 1.36-1.36l.71.71a1 1 0 0 1 0 1.41 1 1 0 0 1 -.71.24z"></path>
            </g>
          </svg>
        </span>
        <span className="moon">
          <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512">
            <path d="m223.5 32c-123.5 0-223.5 100.3-223.5 224s100 224 223.5 224c60.6 0 115.5-24.2 155.8-63.4 5-4.9 6.3-12.5 3.1-18.7s-10.1-9.7-17-8.5c-9.8 1.7-19.8 2.6-30.1 2.6-96.9 0-175.5-78.8-175.5-176 0-65.8 36-123.1 89.3-153.3 6.1-3.5 9.2-10.5 7.7-17.3s-7.3-11.9-14.3-12.5c-6.3-.5-12.6-.8-19-.8z"></path>
          </svg>
        </span>
        <input type="checkbox" className="input" />
        <span className="slider"></span>
      </label>
    </>
  );
};

export default memo(Index);

css的样式

css 复制代码
.switch {
  font-size: 17px;
  position: relative;
  display: inline-block;
  width: 64px;
  height: 34px;
}

.switch input {
  opacity: 0;
  width: 0;
  height: 0;
}

.slider {
  position: absolute;
  cursor: pointer;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background-color: #73c0fc;
  transition: 0.4s;
  border-radius: 30px;
}

.slider:before {
  position: absolute;
  content: '';
  height: 30px;
  width: 30px;
  border-radius: 20px;
  left: 2px;
  bottom: 2px;
  z-index: 2;
  background-color: #e8e8e8;
  transition: 0.4s;
}

.sun svg {
  position: absolute;
  top: 6px;
  left: 36px;
  z-index: 1;
  width: 24px;
  height: 24px;
}

.moon svg {
  fill: #73c0fc;
  position: absolute;
  top: 5px;
  left: 5px;
  z-index: 1;
  width: 24px;
  height: 24px;
}

/* .switch:hover */
.sun svg {
  animation: rotate 15s linear infinite;
}

@keyframes rotate {
  0% {
    transform: rotate(0);
  }

  100% {
    transform: rotate(360deg);
  }
}

/* .switch:hover */
.moon svg {
  animation: tilt 5s linear infinite;
}

@keyframes tilt {
  0% {
    transform: rotate(0deg);
  }

  25% {
    transform: rotate(-10deg);
  }

  75% {
    transform: rotate(10deg);
  }

  100% {
    transform: rotate(0deg);
  }
}

.input:checked + .slider {
  background-color: #183153;
}

.input:focus + .slider {
  box-shadow: 0 0 1px #183153;
}

.input:checked + .slider:before {
  transform: translateX(30px);
}

封装过渡效果的方法

utils/setTheme.ts 这里是利用了 html2canvas的插件,可以将dom元素转换为cavans的功能,然后再画布上进行一些操作,最后是使用了原型销毁获取点击位置的函数实现了过渡的播放

/* 复制代码
import html2canvas from 'html2canvas';
export function toCanvas(el: HTMLDivElement) {
    return new Promise(resolve => {
        // 转换为图片
        const rect = el.getBoundingClientRect();
        html2canvas(el, {
            logging: false, // 禁用日志输出  
            scale: 1, // 放大截图两倍  
            useCORS: true, // 如果需要跨域资源,启用这个选项  
            width: rect.width, // 指定截图的宽度  
            height: rect.height // 指定截图的高度  
        }).then(canvas => {
            const base64Image = canvas.toDataURL('image/png').replace('image/png', 'image/octet-stream');
            const rect = el.getBoundingClientRect();
            //canvas样式设置(位置大小)
            canvas.style.position = "fixed"
            canvas.style.left = rect.left + "px"
            canvas.style.top = rect.top + "px"
            canvas.width = rect.width
            canvas.height = rect.height
            canvas.style.width = rect.width + 'px'
            canvas.style.height = rect.height + 'px'
            const context = canvas.getContext('2d');
            //创建一个Image元素,并将其源设置为转换后的URI
            const img = new Image();
            img.src = base64Image;
            // 当图像加载完成时,将图像绘制到 canvas 上
            img.onload = () => {
                context!.drawImage(img, 0, 0);
                setTimeout(() => {
                    resolve(canvas)
                })
            }
        })
    })
}

export function easeInOutQuint(elapsed: number, initialValue: number, amountOfChange: number, duration: number) {

    if ((elapsed /= duration / 2) < 1) {
        return amountOfChange / 2 * elapsed * elapsed * elapsed * elapsed * elapsed + initialValue;
    }
    return amountOfChange / 2 * ((elapsed -= 2) * elapsed * elapsed * elapsed * elapsed + 2) + initialValue;
}

export function easeInOutQuart(elapsed: number, initialValue: number, amountOfChange: number, duration: number) {
    if ((elapsed /= duration / 2) < 1) {
        return amountOfChange / 2 * elapsed * elapsed * elapsed * elapsed + initialValue;
    }
    return -amountOfChange / 2 * ((elapsed -= 2) * elapsed * elapsed * elapsed - 2) + initialValue;
}

function getMousePos(canvas: HTMLCanvasElement, evt: HTMLLabelElement) {
    const rect = canvas.getBoundingClientRect();
    return {
        x: ((evt.offsetLeft - rect.left) / (rect.right - rect.left) * canvas.width),
        y: ((evt.offsetTop - rect.top) / (rect.bottom - rect.top) * canvas.height)
    };
}

function getMaxRadius(canvas: HTMLCanvasElement) {
    return Math.sqrt(Math.pow(canvas.width, 2) + Math.pow(canvas.height, 2));
}

export const crop = (canvas: HTMLCanvasElement, initialPosition: HTMLLabelElement, { reverse = false }) => {
    const ctx = canvas.getContext('2d');
    const { x, y } = getMousePos(canvas, initialPosition);
    const maxRadius = getMaxRadius(canvas);
    return new Promise(resolve => {
        let progress = 0;
        const duration = 60;
        ctx!.fillStyle = 'rgba(255, 255, 255, 1)';
        ctx!.globalCompositeOperation = reverse ? 'destination-in' : 'destination-out';
        function draw() {
            let radius;
            if (reverse) {
                radius = easeInOutQuint(progress, maxRadius, -maxRadius, duration);

            } else {
                radius = easeInOutQuart(progress, 0, maxRadius, duration);

            }
            ctx!.beginPath();
            ctx!.arc(x, y, radius, 0, Math.PI * 2, false);
            ctx!.fill();
            progress++;

            if (progress < duration) {
                requestAnimationFrame(draw);
            } else {
                resolve(canvas);
            }
        }

        draw();
    })
}

这里解释一下我为什么选择root这个根节点,因为我是后台管理的系统 所以选择了root根节点作为图片展示的dom,确保整个页面可以有过渡效果。

最后就可以查看自己的页面效果了

相关推荐
涔溪34 分钟前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun1 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇1 小时前
ES6进阶知识一
前端·ecmascript·es6
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss
龙猫蓝图2 小时前
vue el-date-picker 日期选择器禁用失效问题
前端·javascript·vue.js
fakaifa2 小时前
CRMEB Pro版v3.1源码全开源+PC端+Uniapp前端+搭建教程
前端·小程序·uni-app·php·源码下载
夜色呦2 小时前
掌握ECMAScript模块化:构建高效JavaScript应用
前端·javascript·ecmascript