使用 react-spring 开发一个智能暗模式切换按钮

最近,我重写了博客右上角的深色模式切换按钮。为此,我也学会了学习 Figma。虽然技术不难,但收获不少。至少我可以根据猫和老虎设计一些简单的SVG图形。
废话不多说,介绍一下我的使用方法 反应弹簧 想要实现如此流畅的切换动画,可以进入下方链接,点击右上角按钮进行体验,
为了蹭一波iPhone 智能岛 热度,我特意用了这个标题 聪明的 这两个字。

demo.webp

小军.im/

准备

写之前先拆解一下组件的元素组成,大致分为以下四个部分:

  1. 整个切换容器,主题切换时动态修改背景颜色
  2. 中间的小球,暗模式是月亮,亮模式是太阳
  3. 左侧的星星,以深色模式显示
  4. 右边的云(姑且称它为云吧,太抽象了),以浅色模式显示

其中1和2这里直接用div写,3和4可以用SVG写。

在黑暗模式的情况下,中间的小球会变成月亮,里面有三个月球坑。我们也直接用div来模拟

SVG我用Figma画的,这里直接提供代码,有能力的也可以自己画一些不同的图形。

 <SVG宽度=“104”高度=“54”视图框=“0 0 104 54”填充=“无”xmlns=“http://www.w3.org/2000/svg”>  
 < path d = "M18.0258 11.2704C18.0258 5.34458 22.8296 0.540771 28.7554 0.540771H93.1331C99.0589 0.540771 103.863 5.34458 103.863 11.2704C103.863 17.1962 99.0589 22 93.1331 22H66.2146C63.3038 22 60.9442 24.3596 60.9442 27.2704V27.2704C60.9442 30.1811 63.3038 32.5408 66.2146 32.5408 H75.1073C81.0331 32.5408 85.8369 37.3446 85.8369 43.2704C85.8369 49.1962 81.0331 54 75.1073 54H10.7296C4.80381 54 0 49.1962 0 43.2704C0 37.3446 4.80381 32.5408 10.7296 32.5408H44.7296C47.6404 32.5408 50 30.1811 50 27.2704V27.2704C50 24.3596 47.6404 22 44.7296 22H28.7554C22.8296 22 18.0258 17.1962 18.0258 11.2704Z" 填充 = "白色" />  
 </ svg >  
 复制代码

星星✨

 <SVG宽度=“89”高度=“77”视图框=“0 0 89 77”填充=“无”xmlns=“http://www.w3.org/2000/svg”>  
 <路径d =“M25 10L31.7523 28.2477L50 35L31.7523 41.7523L25 60L18.2477 41.7523L0 35L18.2477 28.2477L25 10Z”填充=“#C6D0D1”/>  
 <路径d =“M71.5 42L76.2266 54.7734L89 59.5L76.2266 64.2266L71.5 77L66.7734 64.2266L54 59.5L66.7734 54.7734L71.5 42Z”填充=“#C6D0D1”/>  
 <路径d =“M61 0L63.7009 7.29909L71 10L63.7009 12.7009L61 20L58.2991 12.7009L51 10L58.2991 7.29909L61 0Z”填充=“#C6D0D1”/>  
 </ svg >  
 复制代码

组件结构

资源准备好之后,我们先写整个组件的样板代码

 从“反应”导入 { useState }  
  
 功能暗模式切换(){  
 const [isDarkMode, setIsDarkMode] = useState(false)  
  
 // 我们将宽高设置为合适的大小  
 常量开始 = (  
 < svg className = "绝对左-[8px] top-[7px]" 宽度 = "16" 高度 = "14" viewBox = "0 0 89 77" fill = "none" xmlns = "http://www.w3 .org/2000/svg" >  
 <路径d =“M25 10L31.7523 28.2477L50 35L31.7523 41.7523L25 60L18.2477 41.7523L0 35L18.2477 28.2477L25 10Z”填充=“#C6D0D1”/>  
 <路径d =“M71.5 42L76.2266 54.7734L89 59.5L76.2266 64.2266L71.5 77L66.7734 64.2266L54 59.5L66.7734 54.7734L71.5 42Z”填充=“#C6D0D1”/>  
 <路径d =“M61 0L63.7009 7.29909L71 10L63.7009 12.7009L61 20L58.2991 12.7009L51 10L58.2991 7.29909L61 0Z”填充=“#C6D0D1”/>  
 </ svg >  
 )  
  
 // 我们将宽高设置为合适的大小  
 常量云 = (  
 < svg className = "绝对右-[10px] top-[10px]" 宽度 = "15" 高度 = "8" viewBox = "0 0 104 54" fill = "none" xmlns = "http://www.w3 .org/2000/svg" >  
 < path d = "M18.0258 11.2704C18.0258 5.34458 22.8296 0.540771 28.7554 0.540771H93.1331C99.0589 0.540771 103.863 5.34458 103.863 11.2704C103.863 17.1962 99.0589 22 93.1331 22H66.2146C63.3038 22 60.9442 24.3596 60.9442 27.2704V27.2704C60.9442 30.1811 63.3038 32.5408 66.2146 32.5408 H75.1073C81.0331 32.5408 85.8369 37.3446 85.8369 43.2704C85.8369 49.1962 81.0331 54 75.1073 54H10.7296C4.80381 54 0 49.1962 0 43.2704C0 37.3446 4.80381 32.5408 10.7296 32.5408H44.7296C47.6404 32.5408 50 30.1811 50 27.2704V27.2704C50 24.3596 47.6404 22 44.7296 22H28.7554C22.8296 22 18.0258 17.1962 18.0258 11.2704Z" 填充 = "白色" />  
 </ svg >  
 )  
  
 返回 (  
 <div  
 className = "相对 w-[56px] h-[28px] 圆整 p-[5px] 光标指针"  
 onClick = {() => setIsDarkMode(isDarkMode ? 'light' : 'dark')} > {/* stars*/}​​ ​​{starts} {/* cloud*/} {clouds} {/* 圆形行星中middle*/} < div className = "relative w-[18px] h-[18px] rounded-full z-10" > {/* 月球陨石坑*/} < div className = "relative w-full h-full" >  
 < div className = "绝对顶部-[6px] 左-[4px] w-[4px] h-[4px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 < div className = "绝对顶部-[8px] 左-[11px] w-[1px] h-[1px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 < div className = "绝对顶部-[11px] 左-[9px] w-[2px] h-[2px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 </ div >  
 </ div >  
 </ div >  
 )  
 }  
 复制代码

在上面我使用的代码中 尾风CSS ,如果不喜欢,请换成正常款式。

至此,一个基本的框架就完成了,但是现在它完全没有动画,看起来很生硬,然后让它动起来。

动画

我正在使用的动画库是 反应弹簧 ,是一个模仿弹簧效果的动画库,可以让你的组件更加生动,具体可以去文档 官方网站 看。
然后,如果你有时间,你可以看一下这个大佬演讲的视频,里面介绍了类似的运动效果概念。我用中文机器读的。

www.youtube.com/watch?v=1ta…

小球

先从中间球的左右切换动画说起,毕竟是最显眼的一个。

 //整个球  
 常量 nodeStyles = useSpring({  
 x:是黑暗模式? 28 : 0, // 在暗模式下,球体在右侧  
 旋转: isDarkMode ? 0 : 180, // 在黑暗模式下,给他添加旋转动画  
 背景颜色:isDarkMode ? '#c6d0d1' : '#fde047', // 通过颜色区分是月亮还是太阳  
 })  
 // 月球陨石坑,只在黑暗模式下显示,只是透明度的变化  
 const craterStyles = useSpring({  
 不透明度: isDarkMode ? 1 : 0,  
 })  
 复制代码

星星和云

接下来,我将制作星星和云彩的动画

明星动画

 // 我们将SVG中三颗星的路径提取到一个数组中用于useTransition  
 常量星路径 = [  
 'M25 10L31.7523 28.2477L50 35L31.7523 41.7523L25 60L18.2477 41.7523L0 35L18.2477 28.2477L25 10Z',  
 'M71.5 42L76.2266 54.7734L89 59.5L76.2266 64.2266L71.5 77L66.7734 64.2266L54 59.5L66.7734 54.7734L71.5 42Z',  
 'M61 0L63.7009 7.29909L71 10L63.7009 12.7009L61 20L58.2991 12.7009L51 10L58.2991 7.29909L61 0Z',  
 ]  
 // 由于是三颗星,这里我们使用了useTransition钩子,它可以接受一个数组,并在数组内容发生变化时执行相应的动画  
 常量 starPathTransitions = useTransition(isDarkMode ? starPaths : [], {  
 from: { scale: 0, rotate: - 30, opacity: 0 }, // 初始状态  
 enter: { scale: 1, rotate: 0, opacity: 1 }, // 结束状态  
 leave: { scale: 0, rotate: - 30, opacity: 0 }, // 离开状态  
 trail: 150, // 我们错开三颗星来执行动画  
 })  
 复制代码

星形动画同时使用缩放、不透明度和旋转,并且交错运行,看起来有点像 bulingbuling 一种感觉。

云动画

 // 云只有一个元素,就是左右平移+透明度变化,我们还是用useSpring钩子  
 常量 cloudStyles = useSpring({  
 不透明度: isDarkMode ? 0:1,  
 x:是黑暗模式? - 5:0,  
 })  
 复制代码

容器

容器动画很简单,只有一种背景色

 常量 containerStyles = useSpring({  
 背景颜色:isDarkMode ? '#475569' : '#7dd3fc',  
 })  
 复制代码

最终代码

动画配置写好后,绑定到元素上,最终代码如下

 从“反应”导入反应,{ useState}  
 从'react-spring'导入{动画,useSpring,useTransition}  
  
 const config = {质量:3,张力:200,摩擦:30}  
 常量星路径 = [  
 'M25 10L31.7523 28.2477L50 35L31.7523 41.7523L25 60L18.2477 41.7523L0 35L18.2477 28.2477L25 10Z',  
 'M71.5 42L76.2266 54.7734L89 59.5L76.2266 64.2266L71.5 77L66.7734 64.2266L54 59.5L66.7734 54.7734L71.5 42Z',  
 'M61 0L63.7009 7.29909L71 10L63.7009 12.7009L61 20L58.2991 12.7009L51 10L58.2991 7.29909L61 0Z',  
 ]  
  
 功能暗模式切换(){  
 const [isDarkMode, setIsDarkMode] = useState(false)  
  
 常量 starPathTransitions = useTransition(isDarkMode ? starPaths : [], {  
 来自:{ 比例:0,旋转:- 30,不透明度:0 },  
 输入:{比例:1,旋转:0,不透明度:1},  
 离开:{比例:0,旋转:- 30,不透明度:0},  
 足迹:150,  
 })  
  
 常量 cloudStyles = useSpring({  
 不透明度: isDarkMode ? 0:1,  
 x:是黑暗模式? - 5:0,  
 配置,  
 })  
  
 常量 nodeStyles = useSpring({  
 x:是黑暗模式? 28 : 0,  
 旋转: isDarkMode ? 0:180,  
 背景颜色:isDarkMode ? '#c6d0d1' : '#fde047',  
 配置,  
 })  
  
 常量 containerStyles = useSpring({  
 背景颜色:isDarkMode ? '#475569' : '#7dd3fc',  
 配置,  
 })  
  
 const craterStyles = useSpring({  
 不透明度: isDarkMode ? 1 : 0,  
 配置,  
 })  
  
 常量开始 = (  
 < svg className = "绝对左-[8px] top-[7px]" 宽度 = "16" 高度 = "14" viewBox = "0 0 89 77" fill = "none" xmlns = "http://www.w3 .org/2000/svg" > { starPathTransitions((styles, path) => ( // 注意这里不要省略transformBox,否则即使将transformOrigin设置为center,路径也无法按照中心点移动 <animated. path key = {path} style = {{ ...styles , transformBox: 'fill-box', transformOrigin: ' center' }} d = {path} fill = "#C6D0D1" />)) }</ svg >  
 )  
  
 常量云 = (  
 <animated.svg style = {cloudStyles} className = "绝对右-[10px] top-[10px]" width = "15" height = "8" viewBox = "0 0 104 54" fill = "none" xmlns = " http://www.w3.org/2000/svg" >  
 < path d = "M18.0258 11.2704C18.0258 5.34458 22.8296 0.540771 28.7554 0.540771H93.1331C99.0589 0.540771 103.863 5.34458 103.863 11.2704C103.863 17.1962 99.0589 22 93.1331 22H66.2146C63.3038 22 60.9442 24.3596 60.9442 27.2704V27.2704C60.9442 30.1811 63.3038 32.5408 66.2146 32.5408 H75.1073C81.0331 32.5408 85.8369 37.3446 85.8369 43.2704C85.8369 49.1962 81.0331 54 75.1073 54H10.7296C4.80381 54 0 49.1962 0 43.2704C0 37.3446 4.80381 32.5408 10.7296 32.5408H44.7296C47.6404 32.5408 50 30.1811 50 27.2704V27.2704C50 24.3596 47.6404 22 44.7296 22H28.7554C22.8296 22 18.0258 17.1962 18.0258 11.2704Z" 填充 = "白色" />  
 </ animated.svg >  
 )  
  
 返回 (  
 <动画.div  
 样式 = {容器样式}  
 className = "相对 w-[56px] h-[28px] 圆整 p-[5px] 光标指针"  
 onClick = {() => setIsDarkMode(isDarkMode ? 'light' : 'dark')} > {starts} {clouds} < animated.div style = {nodeStyles} className = "relative w-[18px] h-[18px]圆形全 z-10" >  
 <animated.div style = {craterStyles} className = "relative w-full h-full" >  
 < div className = "绝对顶部-[6px] 左-[4px] w-[4px] h-[4px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 < div className = "绝对顶部-[8px] 左-[11px] w-[1px] h-[1px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 < div className = "绝对顶部-[11px] 左-[9px] w-[2px] h-[2px] 圆角-完整 bg-slate-400/50 阴影-内" />  
 </ animated.div >  
 </ animated.div >  
 </ animated.div >  
 )  
 }  
 复制代码

注意,绑定动画到元素时,元素必须是animated.xxx,原生元素必须是 反应弹簧 运动效果仅在包装后可用。
你也可以看到我正在使用 使用弹簧 使用过渡 当通过一个 配置 参数,这个参数可以让你控制动画的显示方式,
可以发现,球在切换时有弹簧回弹作用。可以尝试自己修改参数,看看有什么区别,
我在这里推荐一个 可视化配置工具 .

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。

这篇文章的链接: https://homecpp.art/2820/9593/1748

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/38380/23552111

posted @ 2022-09-21 11:24  哈哈哈来了啊啊啊  阅读(91)  评论(0编辑  收藏  举报