首页 > 其他分享 >基于Vue.js和Vanta.js的动态天空颜色效果实现

基于Vue.js和Vanta.js的动态天空颜色效果实现

时间:2023-10-30 21:01:44浏览次数:42  
标签:Vue currColor Vanta js 60 currMinute && const else

背景

最近在写一个Vue项目,想要在登录界面加一个动态背景效果,搜索之后发现了 Vanta.js(https://www.vantajs.com/ )这个库。Vanta 可以借助 three.js (WebGL) 或 p5.js 渲染动态的3D背景效果,提供了多种预设。

几种效果都挺不错的,最终我决定采用clouds效果。

vanta.js主页

随即我发现这个效果是可以定制的,包括天空颜色、云的颜色、太阳的颜色、太阳光的颜色、太阳光晕的颜色、云的阴影颜色都是可以改的,只要向 Vanta 传递对应的颜色代码即可。

如果你玩过某知名开放世界二次元手游,你大概会像我一样想起该游戏登录界面的天空颜色会随着真实世界中的时间变化。于是我希望在 Vanta 的 clouds 效果中实现同样的效果。

思路

思路很简单,网页加载时,获取当前的时间,然后选定几个时间节点指定颜色,其他的时间的颜色根据这些节点,对RGB插值就可得到。插值得到的结果传给 Vanta 进行渲染。

经过一番查找,我找到了这个问答:https://stackoverflow.com/questions/60196138/lerp-background-colour-based-on-time-of-day

该问题的题主使用的是p5.js,不过目的和我们是一致的。我又找到了知乎上的这个问题:https://www.zhihu.com/question/38869928

这问题下面有答主提供了插值算法。于是我拿来进行修改,使其符合 TypeScript 语法。

实现

先介绍一下几种颜色大致影响什么效果:

  • 天空颜色:不用多解释。
  • 云层颜色:不用多解释。
  • 太阳颜色:主要影响画面中高光部分(即太阳)的颜色。
  • 太阳光线颜色:主要影响云层表面的部分,表现为太阳光投射在云层上。
  • 太阳光晕颜色:主要影响画面中高光外围的天空部分的颜色。
  • 云层阴影颜色:不用多解释。

有如下的工具函数:

// 获取 Vanta.js clouds 效果的颜色的模块
// 插值代码来自:https://www.zhihu.com/question/38869928/answer/78527903

// HTML颜色转RGB颜色
function parseColor(hexString: string) {
  return [
    hexString.substring(1, 3),
    hexString.substring(3, 5),
    hexString.substring(5, 7),
  ].map((s) => { return parseInt(s, 16); })
};

// 将一位补位到两位
function pad(s: string) {
  return (s.length === 1) ? '0' + s : s;
};

// 插值获得颜色
function gradientColors(startHTML: string, endHTML: string, steps: number, gamma: number) {
  let i, j, ms, me, output = [], so = [];
  gamma = gamma || 1;
  const normalize = (channel: number) => {
    return Math.pow(channel / 255, gamma);
  };
  const startRGB = parseColor(startHTML).map(normalize);
  const endRGB = parseColor(endHTML).map(normalize);
  for (i = 0; i < steps; i++) {
    ms = i / (steps - 1);
    me = 1 - ms;
    for (j = 0; j < 3; j++) {
      so[j] = pad(Math.round(Math.pow(startRGB[j] * me + endRGB[j] * ms, 1 / gamma) * 255).toString(16));
    }
    output.push('#' + so.join(''));
  }
  return output;
};

// 获取系统时间,从0点开始计数,以分钟为单位
function getCurrMinute(): number {
  const now = new Date()
  return now.getHours() * 60 + now.getMinutes()
}

Vanta 中 cloud 效果有多个颜色可以指定。对视觉效果影响最大的应该是天空颜色(skyColor)。使用上面的插值函数,可以得到如下的代码:

export function getSkyColor(): string {
  const black = '#121212'
  const purple = '#140248'
  const orange = '#fea443'
  const dusk = '#c88a46'
  const blue = '#58acfa'

  const currMinute = getCurrMinute()
  let currColor = black

  if (currMinute >= 0 && currMinute < 4 * 60) {
    currColor = black
  } else if (currMinute >= 4 * 60 && currMinute < 5 * 60) {
    currColor = gradientColors(black, purple, 60, 1)[currMinute - 4 * 60]
  } else if (currMinute >= 5 * 60 && currMinute < 6 * 60) {
    currColor = gradientColors(purple, orange, 60, 1)[currMinute - 5 * 60]
  } else if (currMinute >= 6 * 60 && currMinute < 9 * 60) {
    currColor = gradientColors(orange, blue, 3 * 60, 1)[currMinute - 6 * 60]
  } else if (currMinute >= 9 * 60 && currMinute < 16 * 60) {
    currColor = blue
  } else if (currMinute >= 16 * 60 && currMinute < 18 * 60) {
    currColor = gradientColors(blue, dusk, 2 * 60, 1)[currMinute - 16 * 60]
  } else if (currMinute >= 18 * 60 && currMinute < 19 * 60) {
    currColor = gradientColors(dusk, purple, 60, 1)[currMinute - 18 * 60]
  } else if (currMinute >= 19 * 60 && currMinute < 20 * 60) {
    currColor = gradientColors(purple, black, 60, 1)[currMinute - 19 * 60]
  } else if (currMinute >= 20 * 60 && currMinute < 24 * 60) {
    currColor = black
  }
  return currColor
}

实际上,不一定所有时间段都需要插值,白天的大部分时间天空颜色是可以保持不变的,深夜和凌晨同理。

白天时,云层应当呈现为白色;到了夜间,云层应该带有灰色,于是我们简单写出获取云层颜色的代码:

export function getCloudColor(): string {
  const grey = '#525252'
  const whiteblue= '#adc1de'

  const currMinute = getCurrMinute()
  let currColor = whiteblue

  if (currMinute >= 0 && currMinute < 4 * 60) {
    currColor = grey
  } else if (currMinute >= 4 * 60 && currMinute < 6 * 60) {
    currColor = gradientColors(grey, whiteblue, 2 * 60, 1)[currMinute - 4 * 60]
  } else if (currMinute >= 6 * 60 && currMinute < 17 * 60) {
    currColor = whiteblue
  } else if (currMinute >= 17 * 60 && currMinute < 19 * 60) {
    currColor = gradientColors(whiteblue, grey, 2 * 60, 1)[currMinute - 17 * 60]
  } else if (currMinute >= 19 * 60 && currMinute < 24 * 60) {
    currColor = grey
  }

  return currColor
}

同理,有获取太阳颜色的代码:

export function getSunColor(): string {
  const white = '#fef0c0'
  const lightorange = '#f5d0a9'
  const black = '#000000'	// 不能直接隐藏太阳,因此夜间必须将太阳设置为黑色来间接隐藏,否则夜间也会有光源,很不自然。

  const currMinute = getCurrMinute()
  let currColor = white

  if (currMinute >= 0 && currMinute < 4 * 60) {
    currColor = black
  } else if (currMinute >= 4 && currMinute < 6 * 60) {
    currColor = gradientColors(black, lightorange, 2 * 60, 1)[currMinute - 4 * 60]
  } else if (currMinute >= 6 * 60 && currMinute < 8 * 60) {
    currColor = gradientColors(lightorange, white, 2 * 60, 1)[currMinute - 6 * 60]
  } else if (currMinute >= 8 * 60 && currMinute < 16 * 60) {
    currColor = white
  } else if (currMinute >= 16 * 60 && currMinute < 18 * 60) {
    currColor = gradientColors(white, lightorange, 2 * 60, 1)[currMinute - 16 * 60]
  } else if (currMinute >= 18 * 60 && currMinute < 19 * 60) {
    currColor = gradientColors(lightorange, black, 60, 1)[currMinute - 18 * 60]
  } else if (currMinute >= 19 * 60 && currMinute < 24 * 60) {
    currColor = black
  }

  return currColor
}

和获取太阳光晕颜色的代码:

export function getSunGlareColor(): string {
  const white = '#fef0c0'
  const orange = '#ffa64d'
  const black = '#000000'	// 解释同上

  const currMinute = getCurrMinute()
  let currColor = white

  if (currMinute >= 0 && currMinute < 4 * 60) {
    currColor = black
  } else if (currMinute >= 4 && currMinute < 6 * 60) {
    currColor = gradientColors(black, orange, 2 * 60, 1)[currMinute - 4 * 60]
  } else if (currMinute >= 6 * 60 && currMinute < 7 * 60) {
    currColor = gradientColors(orange, white, 60, 1)[currMinute - 6 * 60]
  } else if (currMinute >= 7 * 60 && currMinute < 17 * 60) {
    currColor = white
  } else if (currMinute >= 17 * 60 && currMinute < 18 * 60) {
    currColor = gradientColors(white, orange, 60, 1)[currMinute - 17 * 60]
  } else if (currMinute >= 18 * 60 && currMinute < 19 * 60) {
    currColor = gradientColors(orange, black, 60, 1)[currMinute - 18 * 60]
  } else if (currMinute >= 19 * 60 && currMinute < 24 * 60) {
    currColor = black
  }

  return currColor
}

获取太阳光线颜色的我没写,直接用太阳本身颜色代替了。

将上面代码全部合起来,放进一个.ts文件作为一个模块。在需要使用 Vanta 的页面引入模块就可以了。我将这个文件命名为color.ts

下面给出基于 Vue3 的示例。使用的是组合式API[1]

基于 Vue3 的示例

你需要先安装 Vanta.js 和 three.js。

pnpm install vanta
pnpm install three

最新版本也许会有问题。我的 Vanta 版本是 0.5.24,three 版本是 0.121.0,实测是可以用的。

<template>
<!--写一个div作为背景,然后加上 ref="vantaRef" -->
  <div class="main-container" ref="vantaRef" style="height: 100vh;">
    <!-- 在这里编写你的前景模板代码 -->
  </div>
</template>

<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref } from 'vue'
import * as THREE from 'three'
import CLOUDS from "vanta/dist/vanta.clouds.min"

import * as DynamicColor from '@/plugins/util/color';		// 上面的代码形成的color.ts文件

const vantaRef = ref(null)
let vantaEffect: any = null

onMounted(() => {
  const currSkyColor = DynamicColor.getSkyColor()
  const currCloudColor = DynamicColor.getCloudColor()
  const currSunColor = DynamicColor.getSunColor()
  const currSunGlareColor = DynamicColor.getSunGlareColor()

  vantaEffect = CLOUDS({
    el: vantaRef.value,
    THREE: THREE,	// 也可以换成 p5,但我没试过
    skyColor: currSkyColor,
    cloudColor: currCloudColor,
    cloudShadowColor: '#262646',	// 偷懒直接给了个固定值
    sunColor: currSunColor,
    sunlightColor: currSunColor,	// 偷懒用了太阳颜色
    sunGlareColor: currSunGlareColor,
    mouseControls: false,	// 是否允许动画和鼠标手势交互,想启用的话改成true
    touchControls: false,	// 触摸屏交互
    gyroControls: false,	// 加速度交互,应该是晃动手机的效果
    speed: 0.5,		// 动画速度,不喜欢太快的动画,所以设得小一些
  })
})

onBeforeUnmount(() => {
  if (vantaEffect) {
    vantaEffect.destroy()
  }
})

</script>

上面的例子中,有一些颜色偷懒没有写出来。你可以仿照本节的代码来编写。

这里面最有技术含量的可能是选取颜色。这些工具可能会帮到你:

HTML颜色和RGB颜色转换器 https://www.toolhelper.cn/Color/RGBToHex

HTML取色器 https://c.runoob.com/front-end/5449/

Vanta.js 是一个 JavaScript 库。如果你使用 TypeScript,为了正确导入 Vanta 的效果,需要做一个额外的操作(来源见此处):查找项目根目录的tsconfig.json文件,找到其中的include项,根据该项的配置,在合适的位置创建一个以.d.ts结尾的 TypeScript 文件,然后填入如下内容:

// 按照自己使用的效果名对代码进行修改
declare module 'vanta/dist/vanta.clouds.min' {
  const CLOUDS: any;
  export default CLOUDS;
}

具体来说,我在项目的src目录中创建了一个vanta.d目录,在其中创建了vanta.clouds.d.ts文件,然后填入了以上内容。如果你还想要使用其他 Vanta 动画,那么你可以在vanta.d中创建其他文件,如vanta.birds.d.tsvanta.globe.d.ts等,然后对上面的代码进行对应的修改并填入文件。

我的tsconfig.jsinclude项如下:

"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],

我的项目结构如下:

├─.vscode
├─node_modules
├─public
│  └─images
└─src
    └─assets
    ├─components
    │  ├─略
    │  └─略
    ├─plugins
    │  └─略
    └─vanta.d
    	└─vanta.clouds.d.ts

效果展示

我撰写这篇文章的时间(20:32)。其实20:00-次日5:00都是这个样子:

20:32

6点:

6:00

这个颜色确实不太真实,也许你可以改得更好

标签:Vue,currColor,Vanta,js,60,currMinute,&&,const,else
From: https://www.cnblogs.com/eslzzyl/p/17798788.html

相关文章

  • SocketJS使用记录
    SockJS是一个JavaScript库,用于处理WebSocket或WebSocket风格的双向通信。以下是SockJS的一些主要函数及其作用的简要介绍,并附带一些示例:newSockJS(url,[options]):创建SockJS客户端实例。url参数是WebSocket服务器的URL或端点,options是一个可选的配置对象,......
  • vue 将html保存成pdf文件
    结合html2canvas和jspdf插件处理/*eslint-disable*///不使用JQuery版的importhtml2canvasfrom'html2canvas'importJsPDFfrom'jspdf'/***@paramele要生成pdf的DOM元素(容器)*@parampadfNamePDF文件生成后的文件名字**/exportfunc......
  • [Vue学习]使用Vue-cli脚手架快速搭建项目
    安装vue-cli3npminstall-g@vue/cli验证vue版本vue--version 使用vuecreate项目名创建(需要cd进具体的目录)npmrundev(运行)npmrunbuild(编译)(也可以使用vue ui界面图形化方式进行创建) ==================================================可以直接使用下面方式创建项......
  • [Springboot整合thymeleaf]处理js中的路径问题。
    使用了thymeleaf模板引擎之后,html中的标签,都可以直接替换成th:srcth:href但是处理js的中的资源路径并不是像jsp那么简单了。可以通过以下方式解决。<!--处理路径问题--><scriptth:inline="javascript">varpath=[[${#request.contextPath}]]</script><scriptth:inl......
  • JavaWeb-JS基础
    4.JS基础(1)JS的引入方式HTML内部引入将JS代码放在“<script></script>”标签之间在HTML文档中,可以在任意地方放置任意数量的<script>一般将其放在body元素的底部,改善显示速度<script>alert("WY")//JS代码</script>外部.js文件引入在外部单独编写js......
  • 34-Vue脚手架-组件的自定义事件(使用自定义事件优化Todo-List案例)
    组件的自定义事件1.一种组件间通信的方式,适用于子组件===>父组件(这里也可以使用props传递数据进行实现)2.使用场景:A是父组件,B是子组件,B想给A传数据,那么就要在A中给B绑定自定义事件(事件的回调在A中)3.绑定自定义事件:1)第一种方式,在父组件中<!--通过父组件给子组件绑定一个......
  • vue关于render函数如何渲染v-html
    render函数如何渲染v-html,两种方式(适用于搜索关键字高亮) 例子:“互联网数据”搜索关键字"网"==>row.htmlStr:互联<spanclass="search-text">网</span>数据render:(h,{row})=>{//模板组件方式returnh({template:"<span>"+row.htmlStr+"&l......
  • Vue 3中toRaw和markRaw的使用
    文章目录Vue3的响应性系统使用`toRaw`使用`markRaw`使用场景1.与第三方库交互2.提高性能3.避免无限循环总结......
  • vuex
    一、概念:1.为什么要用vuex?传统:每个页面都要去重新请求后台得到将数据存到session、cookie中作用:专门为vue.js设计的集中式状态管理架构.数据仓库,主要管理状态(共用的属性或数据).把数据进行共享,每个页面想用,都可以来调用.......
  • JS_0077:JS 中对象操作 preventExtensions 禁止添加新属性 defineProperty 添加新属性
    1,//这是定义一个对象constnonExtensible={removalbe:true};//这是通过preventExtensions方法令指定对象无法再添加新的属性Object.preventExtensions(nonExtensib......