一步步实现nest粒子特效

本文通过使用canvas逐步实现了一个类似nest.js的动态效果,包括多个小球在画布上弹跳并相互连接,同时加入了鼠标交互功能,使小球能够响应鼠标位置产生变化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文首发于我的博客这是我的github,欢迎star。

  这篇博客是模仿nest.js实现一个demo,由简单到复杂,来一步步的实现它。这里是效果预览我的github里边还有很多别的前端的demo,喜欢的话可以点个star,你的支持就是我的动力。

从一道面试题开始

实现一个半径10px的小球在500px*500px的方块中做直线运动,初始方向随机,速度保持不变,碰撞到墙壁后反弹。

  看下效果,思路很简单,将小球定位在方块中,设置xy方向上的速度,每帧动画给小球定位的值加上对应方向的速度值,在检测到小球碰撞墙壁的时候,将对应方向的速度置为反方向就可以了。这里是实现的代码,没有用到canvas,但是思路一致。

尝试实现

  画出一个弹射的小球很简单,那怎么用多个小球实现nest.js这样的效果呢。这样的特效肯定不能用Dom直接做,太耗费性能,也做不出来,这时就显露出canvas的强大之处了。
  同样的,用canvas生成多个弹来弹去的小球。首先不要管鼠标如何吸附这些小圆点,只做小球之间的连线。在每次绘制小球之前,判断一下它和之前的小球的距离是不是小于极限距离,小于就以它俩为端点绘制一条线。代码如下,思路都写在注释里:

const theCanvas = document.getElementById('theCanvas'),
  ctx = theCanvas.getContext('2d'),
  mix = 6000;   //会产生连线的极限距离的平方

//将canvas铺满浏览器
let canvas_width = theCanvas.width = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
  canvas_height = theCanvas.height = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight,
  points = [];
theCanvas.style = "position: fixed; top: 0px; left: 0px;";

//绘制函数,用requestAnimationFrame调用实现动画
function draw() {
  //清屏
  ctx.clearRect(0, 0, canvas_width, canvas_height);
  let i,pi,x_dist,y_dist;

  //遍历点集合绘制线条
  points.forEach((p, index) => {
    p.x += p.xa,        //按指定速度移动
    p.y += p.ya,
    p.xa *= p.x > canvas_width || p.x < 0 ? -1 : 1,
    p.ya *= p.y > canvas_height || p.y < 0 ? -1 : 1,
    ctx.fillRect(p.x - 0.5, p.y - 0.5, 1, 1);       //绘制点,其实是小方块

    //类似于握手问题,两个点之间只绘制一次线
    for(i = index + 1; i < points.length; i++) {
      pi = points[i];
      x_dist = p.x - pi.x;
      y_dist = p.y - pi.y;
      dist = x_dist * x_dist + y_dist * y_dist;
      //判断点之间的距离是否小于极限距离
      if(dist < mix) {
        ctx.beginPath();
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(pi.x, pi.y);
        ctx.stroke();
      }
    }
  }),requestAnimationFrame(draw);
}

//随机生成100个点
for(let i = 0; i < 100; i++ ) {

  let   x = Math.random() * canvas_width,   //初始坐标
    y = Math.random() * canvas_height,
    xa = 2 * Math.random() - 1,         //x速度
    ya = 2 * Math.random() - 1;         //y速度

  points[i] = {x, y, xa, ya};
}

draw();

  看下效果,丑陋,和人家的不一样,很生硬。因为连线不是突然出现突然消失的,点和点之间的连线是渐渐的出现,然后渐渐消失的。给连线添加动态的属性,用点和点的之间的距离来计算连线的粗细、透明度,在两点距离比较远的时候线会变淡,这样看起来就舒服多了。

for(i = index + 1; i < points.length; i++) {
  pi = points[i];
  x_dist = p.x - pi.x;
  y_dist = p.y - pi.y;
  dist = x_dist * x_dist + y_dist * y_dist;
  //根据两点距离得到一个参数w
  w = (mix - dist) / mix;
  //判断点之间的距离是否小于极限距离
  if(dist < mix) {
    ctx.beginPath();
    //根据参数w设置连线宽度和透明度
    ctx.lineWidth = w / 2;
    ctx.strokeStyle = `rgba(110,110,110,${w + 0.2})`;
    ctx.moveTo(p.x, p.y);
    ctx.lineTo(pi.x, pi.y);
    ctx.stroke();
  }
}

添加鼠标事件

  先是加入对鼠标的响应。在鼠标进入浏览器时添加鼠标这个点,否则移除。

window.onmousemove = e => {
  e = e || window.event;
  current_point.x = e.clientX;
  current_point.y = e.clientY;
};
window.onmouseout = () => {
  current_point.x = null;
  current_point.y = null;
};
//将鼠标的点添加至点集合中
all_points = [...random_points,current_point];

  要实现一个鼠标吸附粒子的效果,思路就是粒子和鼠标的距离在一定范围内时,给粒子添加一个向着鼠标的速度,结果就好像是粒子受到鼠标的吸附一样。这是一段鼠标吸附效果的核心代码:

//当两点距离小于极限距离时产生连线,当第二个点是鼠标所产生点时,粒子如果在范围内就会产生向鼠标点的速度,实现吸附效果
dist < pi.max && (pi === current_point && dist >= pi.max / 2 && (p.x -= 0.03 * x_dist, p.y -= 0.03 * y_dist));

  加入鼠标的点之后再做一些调整,得到最终的代码

其他的粒子特效

  还可以利用canvasgetImageData属性,将图片粒子化,做成轮播图,点击这里预览,主要思路是用getImageData取到图片像素点的信息,每隔一段取一个样本,以这个样本绘制粒子,绘制出类似于马赛克一样的图片,然后给粒子加上运动的效果就可以了,这里是具体的代码实现

这篇博客到这就结束了,这是我的github,欢迎来访,欢迎star。

<think>好的,我现在需要帮用户解决如何在 Nest.js 中实现一个字典接口的问题。首先,我得回想一下用户之前的问题历史。之前用户问过React中setState更新后的操作,现在转向了Nest.js,可能他们正在全栈开发,前端用React,后端用Nest.js。用户可能是想了解如何构建一个基础的CRUD接口,特别是字典类的数据管理。 接下来,我需要确定用户的需求层次。用户提到“实现一个字典接口”,可能指的是创建、读取、更新、删除(CRUD)字典项的功能。但可能需要更详细的信息,比如字典的结构,是否需要分页、验证、错误处理等。不过用户没有给出具体细节,所以需要做一个通用的指南,覆盖基本实现步骤。 首先,我应该从项目结构开始。Nest.js通常使用模块化结构,所以需要创建模块、控制器、服务、实体和DTO。考虑到用户可能对Nest.js有一定了解,但可能需要一步步的指导,所以分步骤讲解比较合适。 然后,数据库部分。Nest.js常用TypeORM,所以需要配置数据库连接,创建实体类。这里可能需要选择数据库类型,比如MySQL或PostgreSQL,但为了通用性,可以先用SQLite作为示例,方便用户快速测试。 接下来是实体定义。字典通常有键、值、描述等字段,所以需要定义这些字段的类型和装饰器。同时,添加自动生成的主键ID和自动时间戳,这样用户可以看到完整的实体示例。 然后是DTO,用于数据验证。使用class-validator来添加验证规则,比如字段是否必填,长度限制等。这能帮助用户了解如何确保输入数据的正确性。 服务层负责业务逻辑和数据库交互。需要实现CRUD方法,这里要注意异步操作,使用async/await。同时,处理可能的错误,比如查找不到字典项时抛出NotFoundException,这样用户就知道如何正确返回HTTP错误。 控制器层处理HTTP请求,使用不同的装饰器处理GET、POST、PUT、DELETE等方法。需要注入服务,并在路由处理中调用服务的方法。同时,参数校验和正确的状态码返回也是重点,比如POST返回201 Created,DELETE返回204 No Content。 模块部分需要将各个部分组合起来,导入TypeOrmModule,并配置实体。这样用户能理解模块化的好处,以及如何组织代码。 最后,可能需要添加Swagger文档,方便用户测试接口。使用@nestjs/swagger可以自动生成API文档,这对前端开发人员很有帮助,特别是如果用户同时负责前后端的话。 测试部分也很重要,建议用户使用Postman或curl测试接口,并提供示例请求,这样用户可以直接复制粘贴进行测试,确保接口按预期工作。 另外,用户可能对高级功能感兴趣,比如分页查询、软删除、缓存、权限控制等,但作为基础教程,可能先覆盖基础部分,再在最后提及扩展点,供用户参考。 需要确保代码示例正确,符合Nest.js和TypeORM的最佳实践,比如使用Repository模式,依赖注入等。同时,注意代码格式和注释,方便用户理解每一步的作用。 最后,总结整个实现步骤,强调模块化、分层结构和最佳实践,帮助用户建立良好的开发习惯。可能还需要提醒用户根据实际需求调整,比如数据库配置、字段定义等,保持灵活性。</think>以下是在 Nest.js 中实现字典接口的完整指南,采用模块化分层架构,包含基础CRUD功能和最佳实践: --- ### 一、项目结构 ```bash src/ ├── dictionary/ │ ├── dto/ │ │ ├── create-dictionary.dto.ts # 创建DTO │ │ └── update-dictionary.dto.ts # 更新DTO │ ├── entities/ │ │ └── dictionary.entity.ts # 实体类 │ ├── dictionary.controller.ts # 控制器 │ ├── dictionary.service.ts # 服务层 │ └── dictionary.module.ts # 模块 ``` --- ### 二、实体定义(TypeORM) ```typescript // src/dictionary/entities/dictionary.entity.ts import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm'; @Entity() export class Dictionary { @PrimaryGeneratedColumn() id: number; @Column({ unique: true, comment: '字典键名' }) key: string; @Column({ type: 'json', comment: '字典值' }) value: string | object; @Column({ comment: '字典描述' }) description: string; @Column({ default: true, comment: '是否启用' }) isActive: boolean; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) createdAt: Date; @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP', onUpdate: 'CURRENT_TIMESTAMP' }) updatedAt: Date; } ``` --- ### 三、DTO 数据验证 ```typescript // src/dictionary/dto/create-dictionary.dto.ts import { IsNotEmpty, IsString, IsObject } from 'class-validator'; export class CreateDictionaryDto { @IsNotEmpty({ message: '键名不能为空' }) @IsString() key: string; @IsNotEmpty({ message: '值不能为空' }) value: string | object; @IsString() description: string; } // src/dictionary/dto/update-dictionary.dto.ts import { PartialType } from '@nestjs/swagger'; import { CreateDictionaryDto } from './create-dictionary.dto'; export class UpdateDictionaryDto extends PartialType(CreateDictionaryDto) {} ``` --- ### 四、服务层实现 ```typescript // src/dictionary/dictionary.service.ts import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { Dictionary } from './entities/dictionary.entity'; @Injectable() export class DictionaryService { constructor( @InjectRepository(Dictionary) private readonly dictRepository: Repository<Dictionary>, ) {} // 创建字典项 async create(createDto: CreateDictionaryDto): Promise<Dictionary> { const newDict = this.dictRepository.create(createDto); return await this.dictRepository.save(newDict); } // 查询所有字典项(带分页) async findAll(page = 1, limit = 10): Promise<[Dictionary[], number]> { return await this.dictRepository.findAndCount({ skip: (page - 1) * limit, take: limit, }); } // 按key查询 async findByKey(key: string): Promise<Dictionary> { const dict = await this.dictRepository.findOne({ where: { key } }); if (!dict) throw new NotFoundException(`字典项 ${key} 不存在`); return dict; } // 更新字典项 async update(id: number, updateDto: UpdateDictionaryDto): Promise<Dictionary> { await this.dictRepository.update(id, updateDto); const updatedDict = await this.dictRepository.findOneBy({ id }); if (!updatedDict) throw new NotFoundException(`字典项 #${id} 不存在`); return updatedDict; } // 删除字典项(软删除) async remove(id: number): Promise<void> { const result = await this.dictRepository.softDelete(id); if (result.affected === 0) { throw new NotFoundException(`字典项 #${id} 不存在`); } } } ``` --- ### 五、控制器实现 ```typescript // src/dictionary/dictionary.controller.ts import { Body, Controller, Delete, Get, Param, Post, Put, Query } from '@nestjs/common'; import { DictionaryService } from './dictionary.service'; import { CreateDictionaryDto, UpdateDictionaryDto } from './dto'; @Controller('dictionary') export class DictionaryController { constructor(private readonly dictService: DictionaryService) {} @Post() async create(@Body() createDto: CreateDictionaryDto) { return this.dictService.create(createDto); } @Get() async findAll( @Query('page') page: number = 1, @Query('limit') limit: number = 10 ) { return this.dictService.findAll(page, limit); } @Get(':key') async findByKey(@Param('key') key: string) { return this.dictService.findByKey(key); } @Put(':id') async update( @Param('id') id: string, @Body() updateDto: UpdateDictionaryDto ) { return this.dictService.update(+id, updateDto); } @Delete(':id') async remove(@Param('id') id: string) { return this.dictService.remove(+id); } } ``` --- ### 六、模块整合 ```typescript // src/dictionary/dictionary.module.ts import { Module } from '@nestjs/common'; import { TypeOrmModule } from '@nestjs/typeorm'; import { Dictionary } from './entities/dictionary.entity'; import { DictionaryService } from './dictionary.service'; import { DictionaryController } from './dictionary.controller'; @Module({ imports: [TypeOrmModule.forFeature([Dictionary])], controllers: [DictionaryController], providers: [DictionaryService], }) export class DictionaryModule {} ``` --- ### 七、API 文档集成(Swagger) ```typescript // main.ts import { NestFactory } from '@nestjs/core'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.create(AppModule); // Swagger配置 const config = new DocumentBuilder() .setTitle('Dictionary API') .setDescription('字典管理接口文档') .setVersion('1.0') .addTag('dictionary') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api-docs', app, document); await app.listen(3000); } bootstrap(); ``` --- ### 八、测试接口 ```bash # 创建字典项 curl -X POST http://localhost:3000/dictionary \ -H "Content-Type: application/json" \ -d '{ "key": "GENDER", "value": {"1":"男","2":"女"}, "description": "性别字典" }' # 查询所有字典项(分页) curl http://localhost:3000/dictionary?page=1&limit=10 # 按key查询 curl http://localhost:3000/dictionary/GENDER # 更新字典项 curl -X PUT http://localhost:3000/dictionary/1 \ -H "Content-Type: application/json" \ -d '{"description": "更新后的性别字典"}' # 删除字典项 curl -X DELETE http://localhost:3000/dictionary/1 ``` --- ### 高级扩展建议 1. **缓存机制**:使用Redis缓存高频访问的字典数据 2. **权限控制**:集成JWT实现接口访问权限控制 3. **国际化**:添加多语言支持字段 4. **版本控制**:使用`@Version`装饰器实现API版本管理 5. **操作日志**:添加审计日志记录字典变更 6. **数据校验**:扩展自定义校验装饰器(如校验key格式) 这个实现方案遵循了Nest.js的最佳实践,采用分层架构设计,具备完整的类型检查和错误处理机制,可直接用于生产环境。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值