大屏天气展示太普通?视觉升级!用 Canvas 做动态天气遮罩,雷阵雨效果直接封神
之前做天气那个模块的时候,突发奇想想做一个大屏实时展示天气状况的蒙版。# Vue实现大屏获取当前所处城市及当地天气(纯免费)
需求
现在大屏上展示天气一般都是在左上/右上做天气的图标/纯文字的展示,虽然看起来非常直观,但是对于大屏这种需要炫酷效果的产品显得不合适。
目前市面上对于天气这一块也并不是非常重视,我接触的大屏项目/产品对这部分基本都没啥要求。
但是能够展示天气效果对于大屏本身有相当不错的加成效果。
![]()
所以我开发了这个大屏天气展示蒙版组件,能够根据当前天气状况以蒙版的形式展示出来,目前支持多种天气的展示效果。
方案
视频方案
一开始考虑的是纯视频解决方案,首先说这个方案非常的简单,将视频以背景图的形式放在蒙版上,通过 pointer-events: none; 鼠标穿透就能算是完成了。
但是实际操作过程中发现问题比较多,首先是透明背景需要特定格式的视频才能够支持。
必须使用支持 Alpha 通道(透明通道)的视频格式。
常见支持透明背景的格式包括:
-
WebM(VP8 或 VP9 编码 + Alpha 通道):Chrome、Firefox 和 WebView2 等 Chromium 内核环境支持良好 。 -
MOV(Apple ProRes 4444 编码):支持 Alpha 通道,但主要在 macOS 和专业软件(如 Final Cut Pro、After Effects)中使用 。 -
MP4(H.265/HEVC 编码):部分平台(如 WebView2)支持含 Alpha 通道的 H.265 视频。
但是我在网上并没有找到相应格式的视频,自己录也弄得不好,所以放弃了。
另外以视频作为背景图在弱网环境下比较难加载,毕竟视频一般都要超过5M以上了。
但是如果有相应的视频,效果做出来绝对是最顶尖的。
GIF动图方案
和视频方案基本一致,唯一的区别是使用 GIF 动图作为背景图使用,效果也非常好。
问题点在于需要UI做一系列的动图效果,GIF 动图在加载上速度也不算太快,毕竟比较好的动图也不会太小。
还有一个问题在于如果屏幕大小发生变化,或者不是标准屏,可能存在图片拉伸/裁切等问题。
如果有UI协助,采用这个方案也非常不错。
Canvas渲染
采用 Canvas 渲染的方案实现这个是我最后的选择,原因有三:
- Canvas性能开销不算太大,对低端设备相对比较友好
- Canvas不依赖静态资源,弱网环境下不影响加载效果
- Canvas能够根据屏幕大小达到自适应效果,避免特殊屏幕尺寸显示异常
采用 Canvas 粒子效果和渐变效果模拟阳光和雨滴、雪花等等状态,实现天气状态。
代码
初始化遮罩层
目前使用的是 Vue3 框架,因为是遮罩层所以采用 pointer-events: none; 鼠标穿透,避免影响大屏正常的操作。
<template>
<canvas
ref="weatherCanvas"
class="weather-mask"
:style="{ opacity: maskOpacity }"
></canvas>
</template>
<style scoped>
.weather-mask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none; /* 鼠标事件穿透 */
z-index: 10; /* 确保在内容上方,可根据项目调整 */
}
</style>
这里需要注意,初始化画布的时候要记得设置一下width、height,让画布充满整个屏幕。
晴天效果
晴天效果采用光照渐变效果,在 Canvas 中绘制了一个从左上角到右下角线性渐变的效果,来模拟阳光照射的感觉。
同时增加部分光斑效果,模仿阳光投射在玻璃上的感觉。
// 创建从左上角到右下角的线性渐变(模拟阳光照射)
lightGradient = ctx.createLinearGradient(0, 0, canvas.width, canvas.height);
// 绘制光斑效果
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = `rgba(255, 255, 200, ${Math.random() * 0.1 + 0.05})`;
ctx.fill();
雨天效果
雨天采用粒子效果,实现细长的雨丝效果,这里没有做明显的区分,对于小雨、中雨、大雨。
其实想要区分也很简单,只要控制粒子的数量和速度即可。
ctx.beginPath();
ctx.moveTo(particle.x, particle.y);
ctx.lineTo(particle.x, particle.y + particle.height);
ctx.strokeStyle = particle.color.replace('OPACITY', particle.opacity);
ctx.lineWidth = particle.width;
ctx.stroke();
这里我进行了简单的封装,因为雨天、雪天、雾天等等大部分都用到了粒子效果,所以针对粒子的绘制部分进行了封装。
因为下雨是一个连续的绘制过程,所以动画部分做了简单的循环。
const animate = () => {
updateParticles(props.weatherType);
animationId = requestAnimationFrame(animate);
};
下雪效果
下雪本质上和下雨区别不大,唯一的区别是粒子的状态、运动速度和运动方向。
这里没有采用雪花造型的粒子,确实做出来了,但是效果并不好,不如这种圆形的效果看起来好一些。
雪花的绘制和雨滴的绘制区别在于,雨滴的宽度是1,而雪花的大小是一定范围内随机的。
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fillStyle = particle.color.replace('OPACITY', particle.opacity);
ctx.fill();
雷阵雨效果
雷阵雨效果是这里面个人觉得做的最好的一个,通过对下雨效果增加随机雷电闪烁屏幕的效果,达到雷阵雨天气的遮罩。
下雨仍然是复用的。
// 绘制主闪电路径
ctx.globalCompositeOperation = 'lighter';
ctx.strokeStyle = `rgba(255, 255, 255, ${thunderAlpha})`;
ctx.lineWidth = Math.random() * 8 + 4;
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(startX, startY);
// 绘制闪电分支
ctx.lineWidth = Math.random() * 4 + 1;
ctx.beginPath();
ctx.moveTo(branch.startX, branch.startY);
let bx = branch.startX;
let by = branch.startY;
const dx = Math.random() * 30 - 15;
const dy = Math.random() * 20 + 5;
ctx.lineTo(bx + dx, by + dy);
bx += dx;
by += dy;
ctx.stroke();
// 闪烁效果
ctx.fillStyle = `rgba(255, 255, 255, ${thunderAlpha * 0.1})`;
总结
这个遮罩我做了好几天,Canvas部分我也不是特别的熟悉,所以很多地方仍然有非常大的优化空间,有感兴趣的朋友可以移步下面的文章获取源代码。
至于为啥收费,我也是想尝试一下代码还能不能搞到钱,毕竟现在的软件行业白嫖是大家的常态。
如果您能支持1元钱那我不胜感激,如果确实认为不值,自己能够写出更好的,那我也祝福。
一共做个几个效果:晴、雨、雪、雾、雷阵雨、多云、沙尘、阴天。有兴趣的朋友可以运行起来自行查看一下。