Vue3+TypeScript项目实战——打造雨雪交加的智慧城市
个人简介
👀个人主页: 前端杂货铺
⚡开源项目: rich-vue3 (基于 Vue3 + TS + Pinia + Element Plus + Spring全家桶 + MySQL)
🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展
📃个人状态: 研发工程师,现效力于中国工业软件事业
🚀人生格言: 积跬步至千里,积小流成江海
🥇推荐学习:🍖开源 rich-vue3 🍍前端面试宝典 🍉Vue2 🍋Vue3 🍓Vue2/3项目实战 🥝Node.js实战 🍒Three.js🌕个人推广:每篇文章最下方都有加入方式,旨在交流学习&资源分享,快加入进来吧
| 内容 | 参考链接 |
|---|---|
| THREE.JS 专栏 | Three.js 入门 |
文章目录
- 前言
- 项目概况
- 源码路径
- 文件结构与职责
- 写在最后
前言
大家好,这里是前端杂货铺。
这篇文章我们使用 Vue3+TypeScript+Three.js 等主流前端技术,打造 雨雪交加的智慧城市 项目。
three.js-雨雪交加的智慧城市

项目源码 => 请点击此处自行获取 [github] rich-vue3
如果此项目对你有些帮助,欢迎给个免费的 Star !!!(Thanks♪(・ω・)ノ)
项目概况
源码路径
该项目已被托管到 rich-vue3 中,具体源码在 rich-vue3 项目的 rich-vue3-webapp/src/views/city-three 路径。
文件结构与职责
下面是该项目涉及到文件的基本结构:
- base/index.css:页面的基础样式
- config/index.ts:存储项目中需要使用的颜色
- effect/…: 各种效果及特效,包括 天空盒子、扩散半球、扩散圆、旋转四棱锥、飞线、文字、雷达、雨、路径运动、烟雾、雪、建筑物外围线条、透明墙等
- enter/initCity.ts:初始化场景、 创建城市实例、监听浏览器变化、动画
- enter/city.ts:城市类,加载城市模型、初始化各种效果、点击聚焦和滑动滑轮缩放
- utils/index.ts:封装加载城市模型的方法
- index.vue:基本 UI,初始化项目的入口

城市类代码如下,在 initEffect() 方法中会创建很多种效果。
import { loadFBX } from "../utils"
import * as THREE from "three"
import * as TWEEN from "@tweenjs/tween.js"
import { SurroundLine } from "@/views/city-three/effect/surroundLine"
import { Background } from "@/views/city-three/effect/background"
import { Radar } from "../effect/radar"
import { Wall } from "../effect/wall"
import { Circle } from "@/views/city-three/effect/circle"
import { Ball } from "@/views/city-three/effect/ball"
import { Cone } from "@/views/city-three/effect/cone"
import { Fly } from "@/views/city-three/effect/fly"
import { Road } from "@/views/city-three/effect/road"
import { Font } from "@/views/city-three/effect/font"
import { Snow } from "@/views/city-three/effect/snow"
import { Rain } from "@/views/city-three/effect/rain"
import { Smoke } from "@/views/city-three/effect/smoke";export class City {private readonly scene: anyprivate readonly camera: anyprivate readonly controls: anyprivate tweenPosition: anyprivate tweenRotation: anyprivate flag: booleanprivate readonly height: { value: number }private readonly time: { value: number }private readonly top: { value: number }private readonly effect: {snow: anyrain: anysmoke: any}constructor(scene: object, camera: object, controls: any) {this.scene = scenethis.camera = camerathis.controls = controlsthis.flag = falsethis.tweenPosition = nullthis.tweenRotation = nullthis.height = {value: 5}this.time = {value: 0}this.top = {value: 0}// 雪、雨、烟雾this.effect = {snow: null,rain: null,smoke: null}this.loadCity()}loadCity() {// 加载城市模型,并且渲染到画布loadFBX("model/beijing.fbx").then((object: any) => {object.traverse((child: any) => {if (child.isMesh) {new SurroundLine(this.scene, child, this.height, this.time)}})this.initEffect()})}// 初始化效果,各个功能点都放在了这里initEffect() {new Background(this.scene)new Radar(this.scene, this.time)new Wall(this.scene, this.time)new Circle(this.scene, this.time)new Ball(this.scene, this.time)new Cone(this.scene, this.top, this.height)new Fly(this.scene, this.time)new Road(this.scene, this.time)new Font(this.scene)this.effect.snow = new Snow(this.scene)this.effect.rain = new Rain(this.scene)this.effect.smoke = new Smoke(this.scene)// 点击选择this.addClick()this.addWheel()}addClick() {let flag = truedocument.onmousedown = () => {flag = truedocument.onmousemove = () => {flag = false}}document.onmouseup = (event) => {if (flag) {this.clickEvent(event)}document.onmousemove = null}}// 场景跟随鼠标坐标缩放addWheel() {const body: HTMLElement = document.body// @ts-ignorebody.onmousewheel = (event: MouseEvent) => {// 鼠标当前的坐标const x = (event.clientX / window.innerWidth) * 2 - 1const y = -(event.clientY / window.innerHeight) * 2 + 1const value = 30const vector = new THREE.Vector3(x, y, 0.5)vector.unproject(this.camera)vector.sub(this.camera.position).normalize()// @ts-ignoreif (event.wheelDelta > 0) {this.camera.position.x += vector.x * valuethis.camera.position.y += vector.y * valuethis.camera.position.z += vector.z * valuethis.controls.target.x += vector.x * valuethis.controls.target.y += vector.y * valuethis.controls.target.z += vector.z * value} else {this.camera.position.x -= vector.x * valuethis.camera.position.y -= vector.y * valuethis.camera.position.z -= vector.z * valuethis.controls.target.x -= vector.x * valuethis.controls.target.y -= vector.y * valuethis.controls.target.z -= vector.z * value}}}// 点击聚焦clickEvent(event: MouseEvent) {// 归一化坐标(将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1))const x = (event.clientX / window.innerWidth) * 2 - 1const y = -(event.clientY / window.innerHeight) * 2 + 1// 创建设备坐标(三维)const standardVector = new THREE.Vector3(x, y, 0.5)// 转化为世界坐标 (将此向量 (坐标) 从相机的标准化设备坐标 (NDC) 空间投影到世界空间)const worldVector = standardVector.unproject(this.camera)// 做序列化const ray = worldVector.sub(this.camera.position).normalize()// 实现点击选中// 创建一个射线发射器,用来发射一条射线const raycaster = new THREE.Raycaster(this.camera.position, ray)// 返回射线碰撞到的物体const intersects = raycaster.intersectObjects(this.scene.children, true)let point3d = nullif (intersects.length) {point3d = intersects[0]}if (point3d) {const proportion = 3// 开始动画修改观察点const time = 1000this.tweenPosition = new TWEEN.Tween(this.camera.position).to({ x: point3d.point.x * proportion, y: point3d.point.y * proportion, z: point3d.point.y * proportion }, time).start()this.tweenRotation = new TWEEN.Tween(this.camera.rotation).to({ x: this.camera.rotation.x, y: this.camera.rotation.y, z: this.camera.rotation.z }, time).start()}}start(delta: number) {for (const key in this.effect) {// @ts-ignorethis.effect[key] && this.effect[key].animation()}if (this.tweenPosition && this.tweenRotation) {this.tweenPosition.update()this.tweenRotation.update()}this.height.value += 0.4if (this.height.value > 160) {this.height.value = 5}this.time.value += deltaif (this.top.value > 15 || this.top.value < 0) {this.flag = !this.flag}this.top.value += this.flag ? -0.8 : 0.8}
}
写在最后
由于本项目涉及到的代码较多,在本篇文章中就不一一讲解了,感兴趣的同学可以去下载项目源码自行学习,有问题的话可以评论区一起讨论交流~
好啦,本篇文章到这里就要和大家说再见啦,祝你这篇文章阅读愉快,你下篇文章的阅读愉快留着我下篇文章再祝!
参考资料:
- Three.js 官方文档
- WebGL+Three.js 入门与实战【作者:慕课网_yancy】

相关文章:
Vue3+TypeScript项目实战——打造雨雪交加的智慧城市
个人简介 👀个人主页: 前端杂货铺 ⚡开源项目: rich-vue3 (基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL) 🙋♂️学习方向: 主攻前端方向,正逐渐往全干发展 …...
经典游戏案例:植物大战僵尸
学习目标:植物大战僵尸核心玩法实现 游戏画面 项目结构目录 部分核心代码 using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.SceneManagement; using Random UnityEngine.Random;public enum Z…...
Go 与 Java 字符编码选择:UTF-8 与 UTF-16 的较量
💝💝💝欢迎莅临我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…...
vscode+picgo+gitee实现Markdown图床
vscode中编辑Markdown文件,复制的图片默认是保存在本地的。当文档上传csdn时,会提示图片无法识别 可以在gitee上创建图床仓库,使用picgo工具上传图片,在Markdown中插入gitee链接的方式来解决该问题。 一、 安装picgo工具 1.1 v…...
【thinkphp问题栏】tp5.0分页技巧
一、调用内置方法paginate thinkphp内置了一个paginate方法支持分页功能 该方法位于library\think\db\Query.php内 /*** 分页查询* param int|array $listRows 每页数量 数组表示配置参数* param int|bool $simple 是否简洁模式或者总记录数* param array $config 配…...
获取时间戳是使用System.currentTimeMillis()还是使用new Date().getTime()(阿里开发规范)?
1.阿里规范 在阿里的Java开发手册中强制要求使用System.currentTimeMillis() 2.为什么(源码详解) new Date().getTime()它实际上也是调用的System.currentTimeMillis(),源码分析。 这个fastTime是它的成员变量,在new Date()的时候就被赋值了。 扩展一…...
仿饿了么加入购物车旋转控件 - 自带闪转腾挪动画 的按钮
, mWidth - mCircleWidth, mHeight - mCircleWidth); canvas.drawRoundRect(rectF, mHintBgRoundValue, mHintBgRoundValue, mHintPaint); //前景文字 mHintPaint.setColor(mHintFgColor); // 计算Baseline绘制的起点X轴坐标 int baseX (int) (mWidth / 2 - mHintPaint.m…...
Docker部署nacos集群
docker拉取nacos镜像,本文使用nacos2.0.3 三台服务器都要执行以下命令 docker pull nacos/nacos-server:v2.2.0准备挂载的日志目录和配置文件目录 日志:mkdir /usr/local/software/nacos/logs 配置文件:/usr/local/software/nacos/conf在配…...
centos查找文件 及 操作写入的进程
du -sh * 查看目录空间占用、发现大文件,确定进程,结束 yum install lsof 安装lsof 查看文件写入的 进程 2. lsof /root/.influxdbv2/engine/data/bab49411e5f7cbce/autogen/1/000000036-000000002.tsm COMMAND PID USER FD TYPE …...
构建高可用Java微服务架构的秘籍
构建高可用Java微服务架构的秘籍 大家好,我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编,也是冬天不穿秋裤,天冷也要风度的程序猿! 随着云计算和分布式系统的快速发展,微服务架构已成为构建大型应用…...
VBA学习(18):VBA制作任意工作表均可使用的聚光灯
在需要制作聚光的工作簿,按<ALTF11>组合键,打开VBE编辑器。在右侧[工程资源管理器窗格]选中ThisWorkbook模块,将以下代码复制粘贴到该模块的代码窗口。 Private Sub Workbook_SheetSelectionChange(ByVal Sh As Object, ByVal Target …...
【STM32-启动文件 startup_stm32f103xe.s】
STM32-启动文件 startup_stm32f103xe.s ■ STM32-启动文件■ STM32-启动文件主要做了以下工作:■ STM32-启动文件指令■ STM32-启动文件代码详解■ 栈空间的开辟■ 栈空间大小 Stack_Size■ .map 文件的详细介绍■ 打开map文件 ■ 堆空间■ PRESERVE8 和 THUMB 指令…...
51学习记录(一)——51介绍及震动感应灯
文章目录 前言一、STC89C522.内部结构及引脚 二、继电器原理及震动传感器原理三、项目搭建及实现 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出 提示:以下是本篇文章正文内容,下面案例可供参考 一、STC89C52 1.简介 所属系列:51单…...
2024GLEE生活暨教育(上海)博览会,8月20-22日,国家会展中心(上海)
2024GLEE生活暨教育(上海)博览会将于8月20-22日在中国国家会展中心(上海)举行,博览会总面积近万平方米,设有美好生活和教育产品两大主力展区,全面覆盖婴幼儿、学龄前、小学、初中、高中、大学、中年、老年各个年龄段的…...
debug调试高级功能 断点、布局 及Android Studio常用快捷按键使用详情
文章目录 debug断点篇:打临时断点(只用一次):alt断点条件断点:在断点上,点击右键,在Condition那里,设置我们需要的值,循环就会自动停到我们设置的那个值那里依赖断点&…...
51单片机STC89C52RC——6.1 中断系统
一,文字层面理解 反正我看下面的几段文字时脑壳没有正常运转。一个头几个大 中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。 当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这…...
Redis源码学习:高性能Hash表的设计与实现
哈希表(Hash)是Redis数据库的数据类型之一,理解哈希表的实现对于掌握Redis非常重要。这篇文章,从哈希冲突和哈希扩展这两个角度,来一步步讲解Redis哈希表的工作原理。 什么是哈希表? 哈希表是一种通过哈希…...
如何防范常见的数据库安全问题
随着数据量的增加和系统的复杂性提高,数据库可能面临多种安全威胁,包括未授权访问、数据泄露、注入攻击等。 1. 未授权访问 未授权访问是指,未经授权的用户对数据库的内容进行访问。这会导致数据泄露、数据篡改或其他安全事故。 针对未授权访问的防范措施如下。 (1)强化…...
[Day 19] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
區塊鏈的數據透明性 區塊鏈技術作為一種分布式賬本技術,因其去中心化、不可篡改和高度透明的特性,已經在各行各業中得到了廣泛應用。在本文中,我們將深入探討區塊鏈的數據透明性,包括其原理、實現方法及相關代碼示例,…...
【Hadoop学习笔记】认识Hadoop
认识Hadoop 从网上找的课程做的笔记,有些图是自己理解画的,可能不正确,可以作为参考,有疑问的地方请直接指出,共同交流。 Hadoop是由Apache基金会开发的一个分布式系统基础架构,主要解决海量数据的存储和海…...
Vim 调用外部命令学习笔记
Vim 外部命令集成完全指南 文章目录 Vim 外部命令集成完全指南核心概念理解命令语法解析语法对比 常用外部命令详解文本排序与去重文本筛选与搜索高级 grep 搜索技巧文本替换与编辑字符处理高级文本处理编程语言处理其他实用命令 范围操作示例指定行范围处理复合命令示例 实用技…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
Qt Widget类解析与代码注释
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this); }Widget::~Widget() {delete ui; }//解释这串代码,写上注释 当然可以!这段代码是 Qt …...
高危文件识别的常用算法:原理、应用与企业场景
高危文件识别的常用算法:原理、应用与企业场景 高危文件识别旨在检测可能导致安全威胁的文件,如包含恶意代码、敏感数据或欺诈内容的文档,在企业协同办公环境中(如Teams、Google Workspace)尤为重要。结合大模型技术&…...
【AI学习】三、AI算法中的向量
在人工智能(AI)算法中,向量(Vector)是一种将现实世界中的数据(如图像、文本、音频等)转化为计算机可处理的数值型特征表示的工具。它是连接人类认知(如语义、视觉特征)与…...
毫米波雷达基础理论(3D+4D)
3D、4D毫米波雷达基础知识及厂商选型 PreView : https://mp.weixin.qq.com/s/bQkju4r6med7I3TBGJI_bQ 1. FMCW毫米波雷达基础知识 主要参考博文: 一文入门汽车毫米波雷达基本原理 :https://mp.weixin.qq.com/s/_EN7A5lKcz2Eh8dLnjE19w 毫米波雷达基础…...
若依登录用户名和密码加密
/*** 获取公钥:前端用来密码加密* return*/GetMapping("/getPublicKey")public RSAUtil.RSAKeyPair getPublicKey() {return RSAUtil.rsaKeyPair();}新建RSAUti.Java package com.ruoyi.common.utils;import org.apache.commons.codec.binary.Base64; im…...
企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...
Monorepo架构: Nx Cloud 扩展能力与缓存加速
借助 Nx Cloud 实现项目协同与加速构建 1 ) 缓存工作原理分析 在了解了本地缓存和远程缓存之后,我们来探究缓存是如何工作的。以计算文件的哈希串为例,若后续运行任务时文件哈希串未变,系统会直接使用对应的输出和制品文件。 2 …...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
