记录--Threejs-着色器实现一个水波纹

news2024/11/18 23:35:16

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

hree.js 是一个基于 WebGL 的 JavaScript 3D 库,用于创建和渲染 3D 图形场景。

一、 图像渲染过程

1、webGL

webGL: WebGL 是一种基于 JavaScript API 的图形库,它允许在浏览器中进行高性能的 3D 图形渲染。webGL的渲染依赖于底层GPU的渲染能力。
通过获取<canvas>元素获取WebGL的上下文,从而获得WebGL API和GPU。GPU 图形处理器:处理图形计算的硬件。GPU运行着一个着色器小程序。包含两种类型的着色器程序,顶点着色器片元着色器

2、着色器

着色器:

3、坐标系

(1)模型空间:物体在其自身坐标系下的位置、大小、方向。
(2)世界空间:所有模型都放置在同一坐标系下的空间。每个物体都有唯一的坐标系。如three.js的AxesHelper是基于世界空间创建的坐标系。
(3)视图空间:相机所在的坐标系。简单来说就是以相机为原点,物体在相机眼中的位置。
(4)投影空间:将3D图形投影到二维屏幕上的坐标系。将3D坐标转化为2D坐标。
各个坐标系之间的转换:通过矩阵变换来完成。例如,将物体从模型空间转换到世界空间,可以使用模型变换矩阵将局部坐标转换为全局坐标。将物体从世界空间转换到视图空间,可以使用相机变换矩阵将全局坐标变换为相机坐标。最后,将视图空间中的坐标投影到屏幕上,可以使用投影变换矩阵将相机坐标变换为裁剪坐标。通过这些矩阵变换,可以将坐标从一个空间转换到另一个空间,从而实现3D图形的渲染和显示。

4、GPU渲染过程

(1)渲染管线:就是将3D坐标转化为屏幕像素(屏幕都是二维的,也就是二维坐标)的过程。分为以下几个阶段。应用阶段:由CPU控制,主要负责数据的准备和处理。CPU将数据发送的GPU,包括图形的顶点坐标、纹理坐标、颜色信息等 。几何阶段:运行在GPU中。将顶点坐标变换到屏幕空间中。光栅化阶段:阶段运行在GPU中。光栅化阶段主要将渲染图元转换为像素,并进行颜色插值、纹理采样等处理,最终输出渲染像素。

(2)GPU具体渲染过程。

 齐次裁剪空间:简单来说就是相机视锥体的范围。如下图

二、着色器材质

three.js中有两个着色器材质ShaderMaterial和原始着色器材质RawShaderMaterial,它是用着色器语言GLSL编写的程序,可以让我们自定义物体的着色器程序,从而实现复杂的效果。 1、ShaderMaterial:材质接收两个着色器,顶点着色器和片元着色器。着色器代码需要我们自己编写,来实现复杂的效果。来看下如何使用。

用着色器材质实现下面这个效果:

qi.gif

搭建目录结构和基础看这里

1、首先搭建一个three.js场景
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>threejs_collision</title>
    <link rel="stylesheet" href="./asstes/css/style.css">
</head>
<body>
    <script src="./main/index.js" type="module"></script>
</body>
</html>

*{
    padding: 0;
    margin: 0;
}
body,html {
    background: green;
}
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
// 创建场景
const scene = new THREE.Scene();
// 创建相机
const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
);
// 设置相机位置
camera.position.set(0, 0, 10);
//将相机添加到
scene.add(camera);

//创建环境光,环境光会均匀的照亮场景中的所有物体。
const light = new THREE.AmbientLight(0x404040);
//将环境光添加到场景
scene.add(light);
// 创建平行光
const directionalLight = new THREE.DirectionalLight();
//设置光源位置
directionalLight.position.set(0, 5, 0);
//添加到场景
scene.add(directionalLight);
//设置光源投射阴影
directionalLight.castShadow= true

// 创建渲染器
const renderer = new THREE.WebGLRenderer();
// 设置渲染器尺寸
renderer.setSize(window.innerWidth, window.innerHeight);
//开启渲染器阴影计算
renderer.shadowMap.enabled = true
//将canvas添加到body中
document.body.appendChild(renderer.domElement);

// 轨道控制器
const controls = new OrbitControls(camera, renderer.domElement);
// 轨道控制器的阻尼感
controls.enableDamping = true;
//辅助坐标轴
const axesHelp = new THREE.AxesHelper();
scene.add(axesHelp);


const clock = new THREE.Clock()
//渲染函数
function render() {
    //阻尼
    controls.update()
    let time = clock.getDelta();
    renderer.render(scene, camera);
    requestAnimationFrame(render);
}
// 初始化渲染函数
render();
// 监听浏览器窗口尺寸变化
window.addEventListener('resize',() => {
    //重新设置相机宽高比
    camera.aspect = window.innerWidth / window.innerHeight;
    //更新相机投影矩阵
    camera.updateProjectionMatrix();
    //重新设置渲染器尺寸
    renderer.setSize(window.innerWidth,window.innerHeight);
    //设置设备像素比
    renderer.setPixelRatio(window.devicePixelRatio)
})
2、创建着色器材质

从上面动图可以看出,是一个平面贴了一张图,然后给这个平面加了wave的效果。
所以,先创建一个平面。

const planeGeometry = new THREE.PlaneGeometry(1,1,64,64);
引入贴图
const textureLoader = new THREE.TextureLoader();
const texture = textureLoader.load(
    require('../asstes/img/texture/xx.jpeg')
)

创建一个shader文件夹存放着色器代码,新建一个两个.glsl文件,来写顶点着色器和片元着色器代码。如下:

image.png
在js文件中引入这两个着色器:

import vertexShaderText from '../shader/basic/vertex.glsl'
import fragmentShaderText from '../shader/basic/fragment.glsl'
创建着色器材质
const material = new THREE.ShaderMaterial({
    // 顶点着色器
    vertexShader:vertexShaderText,
    // 片元着色器
    fragmentShader:fragmentShaderText,
    // 设置两面可见,默认只能看见一面
    side:THREE.DoubleSide,
})
3、编写着色器代码

顶点着色器 使用的是着色器语言(GLSL),不会也没事,知道怎么接收参数,在哪写逻辑就够了。首先要有一个入口点,也就是下面的void main函数,对数据的处理就写在这里面。
shader中有三种类型的变量: uniforms, attributes, 和 varyingsuniforms:从应用程序(CPU)传到着色器的变量(GPU),顶点着色器和片元着色器都能访问,比如我们可以在ShaderMaterial传递uniforms,在着色器程序中接收。用法:接收:uniform float uTime;attributes:与每个顶点关联的变量。例如,顶点位置,法线和顶点颜色都是存储在attributes中的数据。attributes  可以在顶点着色器中访问。用法:attribute vec3 positionVaryings:在顶点着色器和片元着色器中传递数据。可以将顶点着色器处理过的数据通过varyings传给片元着色器。用法:varying vec2 vUv;

// 高精度浮点数
precision highp float;
void main(){
    // vec4 四维向量
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    // projectionMatrix 投影矩阵;viewMatrix 视图矩阵;modelMatrix 模型矩阵;跟上面提到的坐标系对应。这些都是内置的uniform,使用ShaderMaterial会自动到GLSL shader代码中。使用RawShaderMaterial不会自动添加,需要手动接收。
    //gl_Position是一个内置变量,它表示经过投影、视图和模型变换后的顶点位置。
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元着色器:
// 中等精度浮点数
precision mediump float;
void main(){
    // gl_FragColor内置对象,片元的颜色值 vec4是个思维变量这里代表了红色分量、绿色分量、蓝色分量和透明度分量。
    gl_FragColor = vec4(1.0,1.0,0.0,1.0);    
}

效果:注意这个平面的颜色是片元着色器里gl_FragColor对象决定的,现在是写死的(当然也可以写活)。

image.png

接下来给这个平面添加wave的效果:这个平面在X、y轴,通过改变Z轴的坐标来使平面有上下波动的效果,这个波动的效果像不像正弦余弦曲线,可以通过sin,cos实现这个效果。可以通过Uniforms变量将数据传给顶点着色器和片元着色器。

const clock = new THREE.Clock()
//渲染函数
function render() {
    let time = clock.getElapsedTime()
    material.uniforms.uTime.value = time;
}

const material = new THREE.ShaderMaterial({
    uniforms:{
        uTime:{
            value:0
        },
        // 贴图
        uTexture:{
            value: texture
        }
    }
})
顶点着色器程序:
precision mediump float;
uniform float uTime;
//varying:从顶点着色器传递到片元着色器的变量。 将uv传递到片元着色器。uv是二维坐标,是物体顶点在纹理上的映射位置(相当于将一个3维物体展开后的对应的二维位置)。传递给片元着色器可以读取该坐标处的颜色,赋值给gl_FragColor,实现贴图效果。
varying vec2 vUv;
void main(){
    vUv = uv;
    vec4 modelPosition = modelMatrix * vec4(position, 1.0);
    modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;
    modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元着色器程序:
precision mediump float;
// sampler2D类型的纹理变量
uniform sampler2D uTexture;
// 接收顶点着色器传来的uv
varying vec2 vUv;
void main(){
     // texture2D是用于读取纹理颜色值的函数
    vec4 textureColor = texture2D(uTexture,vUv);
    gl_FragColor = textureColor;
    
}

这样就是实现了以上效果。image.png

如果是RawShaderMaterial材质,内置的uniform需要手动去接收,以上代码改成:
顶点着色器程序:

precision mediump float;
// 定义顶点
attribute vec3 position;
//定义位置参数
attribute vec2 uv;
// 传入投影矩阵
uniform mat4 projectionMatrix;
// 传入视图矩阵
uniform mat4 viewMatrix;
// 传入模型矩阵
uniform mat4 modelMatrix;
//接收着色器材质传递的时间参数
uniform float uTime;
// uv传递到片元着色器 varying是从顶点着色器传递到片元着色器的变量
varying vec2 vUv;
void main(){
    vUv = uv;
    vec4 modelPosition = modelMatrix * vec4(position,1.0);
    modelPosition.z = sin((modelPosition.x + uTime) * 10.0) * 0.05;
    modelPosition.z += sin((modelPosition.y + uTime) * 10.0) * 0.05;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}

三、着色器实现一个水波纹

水波纹相对于上面旗帜飘动的效果,多了些随机性。如水波的高度是变化的,波浪的起伏是随机的,高处和低处的颜色不一样,水波波动的大小、频率等。这里用到了一些随机函数。将这些随机性添加给波浪的高度来达到更真实的效果。下面定义了很多参数,这些参数可以自己去调节看看它们是什么作用。

const material = new THREE.ShaderMaterial({
    vertexShader:vertexShaderText,
    fragmentShader:fragmentShaderText,
    side:THREE.DoubleSide,
    uniforms:{
        uTime:{
            value:0
        },
        uWaresFrequency:{
            value:params.uWaresFrequency
        },
        uScale:{
            value:params.uScale
        },
        uNoiseFrequency:{
            value:params.uNoiseFrequency
        },
        uNoiseScale:{
            value: params.uNoiseScale
        },
        uXzScale:{
            value: params.uXzScale
        },
        uLowColor:{
            value:new THREE.Color(params.uLowColor)
        },
        uHighColor: {
            value:new THREE.Color(params.uHighColor)
        },
        uOpacity:{
            value:params.uOpacity
        }
    },
    transparent: true
})

const plane = new THREE.Mesh(planeGeometry,material)
plane.rotation.x = -Math.PI / 2
scene.add(plane)

// 将这些uniforms变量添加到gui在,方便看效果,找到最合适的值。
gui.add(params,'uWaresFrequency').min(1).max(50).step(0.1).onChange(val => {
    material.uniforms.uWaresFrequency.value = val;
});
gui.add(params,'uScale').min(0).max(0.2).step(0.01).onChange(val => {
    material.uniforms.uScale.value = val;
});
gui.add(params,'uNoiseFrequency').min(0).max(100).step(0.1).onChange(val => {
    material.uniforms.uNoiseFrequency.value = val;
});
gui.add(params,'uNoiseScale').min(0).max(5).step(0.01).onChange(val => {
    material.uniforms.uNoiseScale.value = val;
});
gui.add(params,'uXzScale').min(1).max(5).step(0.01).onChange(val => {
    material.uniforms.uXzScale.value = val;
});
gui.addColor(params,'uLowColor').onFinishChange(val => {
    material.uniforms.uLowColor.value = new THREE.Color(val)
})
gui.addColor(params,'uHighColor').onFinishChange(val => {
    material.uniforms.uHighColor.value = new THREE.Color(val)
})
gui.add(params,'uOpacity').min(0).max(1).onChange(val => {
    material.uniforms.uOpacity.value = val;
})
顶点着色器程序:里面的函数都是从 这本书里抄的
uniform float uTime;
uniform float uWaresFrequency;
uniform float uScale;
uniform float uNoiseFrequency;
uniform float uNoiseScale;
uniform float uXzScale;
varying float vElevation;

float random (vec2 st) {
    return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}
// 旋转函数
vec2 rotate(vec2 uv, float rotation, vec2 mid)
{
    return vec2(
    cos(rotation) * (uv.x - mid.x) + sin(rotation) * (uv.y - mid.y) + mid.x,
    cos(rotation) * (uv.y - mid.y) - sin(rotation) * (uv.x - mid.x) + mid.y
    );
}

// 2d噪声函数 
float noise (in vec2 st) {
    vec2 i = floor(st);
    vec2 f = fract(st);
    float a = random(i);
    float b = random(i + vec2(1.0, 0.0));
    float c = random(i + vec2(0.0, 1.0));
    float d = random(i + vec2(1.0, 1.0));
    vec2 u = f*f*(3.0-2.0*f);
    return mix(a, b, u.x) +
    (c - a)* u.y * (1.0 - u.x) +
    (d - b) * u.x * u.y;
}
// 随机函数
vec4 permute(vec4 x)
{
    return mod(((x*34.0)+1.0)*x, 289.0);
}
vec2 fade(vec2 t)
{
    return t*t*t*(t*(t*6.0-15.0)+10.0);
}
float cnoise(vec2 P)
{
    vec4 Pi = floor(P.xyxy) + vec4(0.0, 0.0, 1.0, 1.0);
    vec4 Pf = fract(P.xyxy) - vec4(0.0, 0.0, 1.0, 1.0);
    Pi = mod(Pi, 289.0); // To avoid truncation effects in permutation
    vec4 ix = Pi.xzxz;
    vec4 iy = Pi.yyww;
    vec4 fx = Pf.xzxz;
    vec4 fy = Pf.yyww;
    vec4 i = permute(permute(ix) + iy);
    vec4 gx = 2.0 * fract(i * 0.0243902439) - 1.0; // 1/41 = 0.024...
    vec4 gy = abs(gx) - 0.5;
    vec4 tx = floor(gx + 0.5);
    gx = gx - tx;
    vec2 g00 = vec2(gx.x,gy.x);
    vec2 g10 = vec2(gx.y,gy.y);
    vec2 g01 = vec2(gx.z,gy.z);
    vec2 g11 = vec2(gx.w,gy.w);
    vec4 norm = 1.79284291400159 - 0.85373472095314 * vec4(dot(g00, g00), dot(g01, g01), dot(g10, g10), dot(g11, g11));
    g00 *= norm.x;
    g01 *= norm.y;
    g10 *= norm.z;
    g11 *= norm.w;
    float n00 = dot(g00, vec2(fx.x, fy.x));
    float n10 = dot(g10, vec2(fx.y, fy.y));
    float n01 = dot(g01, vec2(fx.z, fy.z));
    float n11 = dot(g11, vec2(fx.w, fy.w));
    vec2 fade_xy = fade(Pf.xy);
    vec2 n_x = mix(vec2(n00, n01), vec2(n10, n11), fade_xy.x);
    float n_xy = mix(n_x.x, n_x.y, fade_xy.y);
    return 2.3 * n_xy;
}

void main() {
    vec4 modelPosition = modelMatrix * vec4(position,1.0);
    // 波浪高度
    float elevation = sin(modelPosition.x * uWaresFrequency) * sin(modelPosition.z * uWaresFrequency * uXzScale);
    elevation += cnoise(vec2(modelPosition.xz*uNoiseFrequency+uTime))
    *uNoiseScale;
    elevation *= uScale;
    // 传到片元着色器
    vElevation = elevation;
    modelPosition.y += elevation;
    gl_Position = projectionMatrix * viewMatrix * modelPosition;
}
片元着色器程序:
varying float vElevation;
uniform vec3 uLowColor;
uniform vec3 uHighColor;
uniform float uOpacity;
void main(){
    float a = (vElevation + 1.0) / 2.0;
    // 混合颜色
    vec3 color = mix(uLowColor,uHighColor,a);
    gl_FragColor = vec4(color,uOpacity);
}
最终效果(效果可以调节参数,调到自己满意的效果):
waterWave.gif

本文转载于:

https://juejin.cn/post/7248982532728864825

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/706732.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【STM32智能车】电机控制

【STM32智能车】电机控制 PWMPWM基本用法&#xff1a; 电机驱动基本控制基本状态 欢迎收看由咸鱼菌工作室出品的STM32系列教程。本篇内容主要电机控制 PWM 我们要控制电机&#xff0c;就要先了解一下PWM。 PWM(Pulse Width Modulation)控制——脉冲宽度调制技术&#xff0c;通…

全开源的 agv 小车

最近github 有 一个开源的stm32 大荷载小车&#xff0c;从pcb 到代码到cad 文件全部开源 代码在 git 小车的实物图 小车的cad 模型 小车的 abaqus 力学分析模型 pcb 图 控制板接线图 全开源小车 代码在 git

港科夜闻|香港科大(广州)与特斯联共建研究中心,打造国际领先的创新联合体...

关注并星标 每周阅读港科夜闻 建立新视野 开启新思维 1、香港科大(广州)与特斯联共建研究中心&#xff0c;打造国际领先的创新联合体。6月27日&#xff0c;“数字世界”联合研究中心正式揭牌成立&#xff0c;这个由香港科大(广州)与特斯联共同打造的研究中心&#xff0c;旨在推…

hive中的datagrip和beeline客户端的权限问题

hive中的datagrip和beeline客户端的权限问题 使用ranger和kerberos配置了hadoop和hive&#xff0c;今天想用来测试其权限 测试xwq用户&#xff1a; 1.首先添加xwq用户权限&#xff0c;命令如下&#xff1a; useradd xwq -G hadoop echo xwq | passwd --stdin xwq echo xwq …

【笔记】EDA学习笔记

网课&#xff1a;立创EDA&#xff08;专业版&#xff09;电路设计与制作快速入门 资料&#xff1a;pan.baidu.com/s/1UlcfvAZ13s_wOHo3cvNQxA?pwdb9x8 提取码&#xff1a;b9x8 解压密码&#xff1a;123456 1.创建项目 略 2.原理图设计环境设置 2.1设置 默认网格尺寸&#…

阿里云云效 流水线发布spring cloud项目及Vue项目

1. 单体spring boot 项目流水线主机部署 1.1 创建流水线 页面地址 我们选择模板,当然也可以自己新建一个 1.2 添加流水线源 这里是根据自己的代码仓库决定的,我用的gitee,需要添加服务连接 添加服务连接 新建服务授权/证书 点击新建自动跳转至gitee认证页面,同意授权即可 授权…

网安等保 | 主机安全之KylinOS银河麒麟服务器配置优化与安全加固基线文档脚本分享...

欢迎关注「全栈工程师修炼指南」公众号 点击 &#x1f447; 下方卡片 即可关注我哟! 设为「星标⭐」每天带你 基础入门 到 进阶实践 再到 放弃学习&#xff01; “ 花开堪折直须折&#xff0c;莫待无花空折枝。 ” 作者主页&#xff1a;[ https://www.weiyigeek.top ] 博客&…

线程不安全举例

1、举例说明集合类线程不安全 &#xff08;1&#xff09;查看源码可证明 看ArrayList源码 没有sync、lock&#xff0c;线程不安全 &#xff08;2&#xff09;创建多个线程写入读取数据 List<String> list new ArrayList<>(); for (int i 1; i <30 ; i) {n…

2023年Web安全学习路线总结!430页Web安全学习笔记(附PDF)

关键词&#xff1a;网络安全入门、渗透测试学习、零基础学安全、网络安全学习路线、web安全攻防笔记、渗透测试路线图 网络安全的范畴很大&#xff0c;相较于二进制安全等方向的高门槛、高要求&#xff0c;Web安全体系比较成熟&#xff0c;在现阶段来看&#xff0c;但凡有自己…

k8s下安装redis

一、使用helm安装redis 执行以下命令添加redis的repo helm repo add bitnami https://charts.bitnami.com/bitnami 创建的master和replica pod的默认size是8Gi&#xff0c;如果k8s的node没有足够的空间&#xff0c;会抛出如下错误&#xff1a;default-scheduler 0/3 nodes …

安全防御 --- SSL VPN

附&#xff1a;无线项目介绍 SSL VPN 有浏览器的设备就可以使用SSL&#xff0c;进而使用SSL VPN。无需担心客户端问题&#xff0c;所以SSL VPN也称为无客户端VPN。SSL VPN在client to lan场景下特别有优势。 实际实现过程&#xff08;基于TCP实现&#xff09; &#xff08;1&…

Nginx SSL使用自制证书

1. 生成证书 keytool -genkey -v -alias <Alias别名> -keyalg RSA -keystore <KeyStore文件> -validity <有效期> keytool -genkey -v -alias nginx -keyalg RSA -keystore nginx.keystore -validity 36500 alias别名为 nginxkeystore文件为 nginx.keystore…

C/C++内存管理(内存分布、动态内存分配、动态内存分配与释放、内存泄漏等)

喵~ 内存之5大区&#xff08;栈区、堆区、静态区、常量区、代码区&#xff09;C/C中各自的内存分配操作符内存泄露?内存泄漏检测方法 内存之5大区&#xff08;栈区、堆区、静态区、常量区、代码区&#xff09; 1、栈区&#xff08;stack&#xff09;&#xff1a;由编译器自动分…

避雷投稿!这3本SCIE期刊处于On Hold状态!有剔除风险!

今年3月份科睿唯安剔除的大量期刊中&#xff0c;有几本期刊之前被WOS数据库标记为“On Hold”状态&#xff0c;后面也出现了一些期刊解除“On Hold”状态的情况。 目前&#xff0c;WOS数据库查询有3本SCIE期刊正处于“On Hold”状态&#xff0c;这是什么信号呢&#xff1f;对于…

org.apache.thrift.transport.TTransportException: GSS initiate failed

hive配置kerberos發現錯誤&#xff0c;就所有的配置都配好了&#xff0c;但是使用datagrip連接hive時報錯如下&#xff1a; org.apache.thrift.transport.TTransportException: GSS initiate failed 後面是沒有認證的原因&#xff0c;使用kinit 認證即可 還有一種情況是 我使…

7.1下周黄金非农数据是否会影响美联储七月暂停加息?

近期有哪些消息面影响黄金走势&#xff1f;下周黄金多空该如何研判&#xff1f; ​黄金消息面解析&#xff1a;周五(6月30日)&#xff0c;现货黄金价格收盘上涨&#xff0c;盘中一度升破1920美元关口。美国疲软的个人消费支出推动了美国国债收益率的下降&#xff0c;从而导致美…

libevent实践04:监听管道第二季

一 本次实例使用函数简介 事件集合初始化&#xff1a; struct event_base *event_init(void); 示例&#xff1a; struct event_base *base event_init(); 单个事件初始化 void event_set(struct event *ev, evutil_socket_t fd, short events,void (*callback)(evutil_s…

两小时快速入门 TypeScript 基础(二)面向对象

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;也会涉及到服务端&#xff08;Node.js 等&#xff09; &#x1f4c3;个人状态&#xff1a; 2023届本科毕业生&#xff0c;已拿多个前端 offer&#x…

【实战】 二、React 与 Hook 应用:实现项目列表 —— React17+React Hook+TS4 最佳实践,仿 Jira 企业级项目(二)

文章目录 一、项目起航&#xff1a;项目初始化与配置二、React 与 Hook 应用&#xff1a;实现项目列表1.新建文件2.状态提升3.新建utils4.Custom Hook 学习内容来源&#xff1a;React React Hook TS 最佳实践-慕课网 相对原教程&#xff0c;我在学习开始时&#xff08;2023.0…

【每天40分钟,我们一起用50天刷完 (剑指Offer)】第十二天 12/50

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…