three.js实现3D汽车展厅效果展示

今天简单实现一个three.js的小Demo,加强自己对three知识的掌握与学习,只有在项目中才能灵活将所学知识运用起来,话不多说直接开始。

项目搭建

本案例还是借助框架书写three项目,借用vite构建工具搭建vue项目,搭建完成之后,用编辑器打开该项目,在终端执行 npm i 安装一下依赖,安装完成之后终端在安装 npm i three 即可。

因为我搭建的是vue3项目,为了便于代码的可读性,所以我将three.js代码单独抽离放在一个组件当中,在App根组件中进入引入该组件。具体如下:

xml 复制代码
<template>
  <!-- 3D汽车展厅 -->
  <CarShowroom></CarShowroom>
</template>
 
<script setup>
import CarShowroom from './components/CarShowroom.vue';
</script>
 
<style lang="less">
  *{
    margin: 0;
    padding: 0;
  }
</style>

初始化three.js代码

three.js开启必须用到的基础代码如下:

导入three库

javascript 复制代码
import * as THREE from 'three'

初始化场景

arduino 复制代码
const scene = new THREE.Scene()

初始化相机

dart 复制代码
// 创建相机
const camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,0.1,1000)
camera.position.set(4.25,1.4,-4.5)

初始化渲染器

javascript 复制代码
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth,window.innerHeight)
document.body.appendChild(renderer.domElement)

监听屏幕大小的改变,修改渲染器的宽高和相机的比例

javascript 复制代码
window.addEventListener("resize",()=>{ 
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})

导入轨道控制器

javascript 复制代码
// 添加轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
// 添加控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true // 设置控制阻尼

设置渲染函数

scss 复制代码
// 设置渲染函数
const render = (time) =>{ 
  controls.update()
  renderer.render(scene,camera)
  requestAnimationFrame(render)
}
render()

ok,写完基础代码之后,接下来开始具体的Demo实操。

加载汽车模型

通过使用模型加载器GLTFLoader,然后使用DRACOLoader加载Draco压缩过的模型可以显著减小模型文件体积,从而加快加载速度和提高用户体验。代码如下:

javascript 复制代码
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
 
// 加载汽车模型
const loader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/")
loader.setDRACOLoader(dracoLoader)
loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  scene.add(gltf.scene)
})
复制代码
模型加载完成,画面如下:

因为没有灯光,所以我们需要给一个灯光让模型展现出来,这里设置一下环境光源:

csharp 复制代码
// 设置环境光源
const ambientLight = new THREE.AmbientLight('#fff',0.5)
scene.add(ambientLight)

设置展厅效果

这里通过three库中自带的一些模型来实现展厅的效果,如下:

设置地板样式

php 复制代码
// 设置地板样式
const floorGeometry = new THREE.PlaneGeometry(20,20)
const floormaterial = new THREE.MeshPhysicalMaterial({
  side: THREE.DoubleSide,
  color: 0x808080,
  metalness: 0, // 设置金属度
  roughness: 0.1, // 设置粗糙度
  wireframe: false // 关闭网格线
})
const mesh = new THREE.Mesh(floorGeometry,floormaterial)
mesh.rotation.x = Math.PI / 2
scene.add(mesh)

底部样式设置完,设置一个圆柱体将整个地板进行包裹:

php 复制代码
// 设置圆柱体模拟展厅
const cylinder = new THREE.CylinderGeometry(12,12,20,32)
const cylindermaterial = new THREE.MeshPhysicalMaterial({
  color: 0x6c6c6c,
  side: THREE.DoubleSide
})
const cylinderMesh = new THREE.Mesh(cylinder,cylindermaterial)
scene.add(cylinderMesh)

接下来在圆柱体中设置一个聚光灯,让聚光灯偏垂直照射汽车模型,如下:

ini 复制代码
// 设置聚光灯(让汽车更具有立体金属感)
const spotLight = new THREE.SpotLight('#fff',2)
spotLight.angle = Math.PI / 8 // 散射角度,和水平线的夹角
spotLight.penumbra = 0.2 // 横向,聚光锥的半影衰减百分比
spotLight.decay = 2 // 纵向,沿着光照距离的衰减量
spotLight.distance = 30
spotLight.shadow.radius = 10
spotLight.shadow.mapSize.set(4096,4096)
spotLight.position.set(-5,10,1)
spotLight.target.position.set(0,0,0) // 光照射的方向
spotLight.castShadow = true
scene.add(spotLight)

为了不让展厅穿帮,这里将控制器的缩放以及旋转角度进行一个限制,让其只能在展厅中灵活查看而不能跑到展厅外面去:

ini 复制代码
controls.maxDistance = 10 // 最大缩放距离
controls.minDistance = 1 // 最小缩放距离
controls.minPolarAngle = 0 // 最小旋转角度
controls.maxPolarAngle = 85 / 360 * 2 * Math.PI // 最大旋转角度

设置GUI面板动态控制车身操作

这里我使用three.js库中自带的gui库,来动态的改变车身相关操作,因为我仅仅是控制车身材质和玻璃材质相关的数据操作,这里就线设置一下其相关的材质:

php 复制代码
// 车身材质
let bodyMaterial = new THREE.MeshPhysicalMaterial({
  color: 'red',
  metalness: 1,
  roughness: 0.5,
  clearcoat: 1.0,
  clearcoatRoughness: 0.03
})
// 玻璃材质
let glassMaterial = new THREE.MeshPhysicalMaterial({
  color: '#793e3e',
  metalness: 0.25,
  roughness: 0,
  transmission: 1.0 // 透光性
})

在glb模型中,通过traverse函数遍历场景中的所有对象(包括Mesh、Group、Camera、Light等),并对这些对象进行相应操作或处理(这里的门操作后面会讲解到):

javascript 复制代码
loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  const carModel = gltf.scene
  carModel.rotation.y = Math.PI
  carModel.traverse((obj)=>{
    if(obj.name === 'Object_103' || obj.name === 'Object_64' || obj.name === 'Object_77'){
      // 车身
      obj.material = bodyMaterial
    }else if(obj.name === 'Object_90'){
      // 玻璃
      obj.material = glassMaterial
    }else if(obj.name === 'Empty001_16' || obj.name === 'Empty002_20'){
      // 门
      // doors.push(obj)
    }else{
      return true
    }
  })
  scene.add(gltf.scene)
})

最后得到的结果如下:

接下来通过控制面板来动态的监视汽车模型的车身和玻璃材质:

ini 复制代码
// 设置gui模板控制
// 修改默认面板名称
gui.domElement.parentNode.querySelector('.title').textContent = '3D汽车动态操作'
 
const bodyChange = gui.addFolder("车身材质设置")
bodyChange.close() // 默认关闭状态
bodyChange.addColor(bodyMaterial,'color').name('车身颜色').onChange(value=>{
  bodyMaterial.color.set(value)
})
bodyChange.add(bodyMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  bodyMaterial.metalness = value
})
bodyChange.add(bodyMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  bodyMaterial.roughness = value
})
bodyChange.add(bodyMaterial,'clearcoat',0,1).name('清漆强度').onChange(value=>{
  bodyMaterial.clearcoat = value
})
bodyChange.add(bodyMaterial,'clearcoatRoughness',0,1).name('清漆层粗糙度').onChange(value=>{
  bodyMaterial.clearcoatRoughness = value
})
const glassChange = gui.addFolder("玻璃设置")
glassChange.close() // 默认关闭状态
glassChange.addColor(glassMaterial,'color').name('玻璃颜色').onChange(value=>{
  glassMaterial.color.set(value)
})
glassChange.add(glassMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  glassMaterial.metalness = value
})
glassChange.add(glassMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  glassMaterial.roughness = value
})
glassChange.add(glassMaterial,'transmission',0,1).name('透光性').onChange(value=>{
  glassMaterial.transmission = value
})

车门操作与车身视角展示

这里依然用GUI控制面板来动态实现开关车门以及车内车外视角动态切换的操作,如下:

csharp 复制代码
var obj = { carRightOpen,carLeftOpen,carRightClose,carLeftClose,carIn,carOut }
// 设置车身动态操作
const doChange = gui.addFolder("车身动态操作设置")
doChange.close() // 默认关闭状态
doChange.add(obj, "carLeftOpen").name('打开左车门')
doChange.add(obj, "carRightOpen").name('打开右车门')
doChange.add(obj, "carLeftClose").name('关闭左车门')
doChange.add(obj, "carRightClose").name('关闭右车门')
doChange.add(obj, "carIn").name('车内视角')
doChange.add(obj, "carOut").name('车外视角')

每个操作都对应一个函数,如下:

php 复制代码
// 打开左车门
const carLeftOpen = () => { 
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[1])
}
// 打开右车门
const carRightOpen = () => { 
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[0])
}
// 关闭左车门
const carLeftClose = () => { 
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[1])
}
// 关闭右车门
const carRightClose = () => { 
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[0])
}
 
// 车内视角
const carIn = () => {
  setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
}
// 车外视角
const carOut = () => {
  setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
}

这里使用了补间动画tween.js,其github网址为 github.com/tweenjs/twe... ,终端安装其第三方插件之后,直接引入即可,如下(这里不再过多介绍该库的使用,想学习的可以自行寻找其官方文档学习):

接下来借助tween.js库实现补间动画,如下:

scss 复制代码
// 设置补间动画
const setAnimationDoor = (start, end, mesh) => {
  const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
    mesh.rotation.x = that.x
  })
  tween.start()
}
const setAnimationCamera = (start, end) => {
  const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
      //  camera.postition  和 controls.target 一起使用
      camera.position.set(that.cx, that.cy, that.cz)
      controls.target.set(that.ox, that.oy, that.oz)
  })
  tween.start()
}

最终实现的效果如下:

点击查看车内视角的话,画面如下:

设置手动点击打开关闭车门

通过设置监听点击事件函数来动态实现打开关闭车门:

scss 复制代码
// 设置点击打开车门的动画效果
window.addEventListener('click', onPointClick);
function onPointClick(event) {
  let pointer = {}
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
  var vector = new THREE.Vector2(pointer.x, pointer.y)
  var raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(vector, camera)
  let intersects = raycaster.intersectObjects(scene.children);
  intersects.forEach((item) => {
    if (item.object.name === 'Object_64' || item.object.name === 'Object_77') {
      if (!carStatus || carStatus === 'close') {
        carLeftOpen()
        carRightOpen()
      } else {
        carLeftClose()
        carRightClose()
      }
    }
  })
}

然后给每个车门设置汽车状态,如下:

设置图片背景

为了让展厅更具有视觉效果,接下来设置一个画面背景让其更具有画面感,如下:

ini 复制代码
// 创建聚光灯函数
const createSpotlight = (color) => {
  const newObj = new THREE.SpotLight(color, 2);
  newObj.castShadow = true;
  newObj.angle = Math.PI / 6;;
  newObj.penumbra = 0.2;
  newObj.decay = 2;
  newObj.distance = 50;
  return newObj;
}
 
// 设置图片背景
const spotLight1 = createSpotlight('#ffffff');
const texture = new THREE.TextureLoader().load('src/assets/imgs/奥特曼.jpg')
spotLight1.position.set(0, 3, 0);
spotLight1.target.position.set(-10, 3, 10)
spotLight1.map = texture
const lightHelper = new THREE.SpotLightHelper(spotLight1);
scene.add(spotLight1);

最终呈现的效果如下:

demo做完,给出本案例的完整代码:(获取素材也可以私信博主)

ini 复制代码
<script setup>
import * as THREE from "three"
import { ref, reactive } from 'vue'
// 添加轨道控制器
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader";
// 引入gui.js库
import { GUI } from 'three/examples/jsm/libs/lil-gui.module.min.js'
// 引入tween.js库
import * as TWEEN from '@tweenjs/tween.js'
// 实例化一个gui对象
const gui = new GUI()
 
// 创建场景
const scene = new THREE.Scene()
// 创建相机
const camera = new THREE.PerspectiveCamera(40,window.innerWidth / window.innerHeight,0.1,1000)
camera.position.set(4.25,1.4,-4.5)
// 创建渲染器
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(window.innerWidth,window.innerHeight)
renderer.shadowMap.enabled = true // 接收阴影
document.body.appendChild(renderer.domElement)
 
// 添加控制器
const controls = new OrbitControls(camera,renderer.domElement)
controls.enableDamping = true // 设置控制阻尼
controls.maxDistance = 10 // 最大缩放距离
controls.minDistance = 1 // 最小缩放距离
controls.minPolarAngle = 0 // 最小旋转角度
controls.maxPolarAngle = 85 / 360 * 2 * Math.PI // 最大旋转角度
 
// 监听页面变化
window.addEventListener("resize",()=>{ 
  renderer.setSize(window.innerWidth,window.innerHeight)
  camera.aspect = window.innerWidth/window.innerHeight
  camera.updateProjectionMatrix()
})
 
// 车身材质
let bodyMaterial = new THREE.MeshPhysicalMaterial({
  color: 'red',
  metalness: 1,
  roughness: 0.5,
  clearcoat: 1.0,
  clearcoatRoughness: 0.03
})
// 玻璃材质
let glassMaterial = new THREE.MeshPhysicalMaterial({
  color: '#793e3e',
  metalness: 0.25,
  roughness: 0,
  transmission: 1.0 // 透光性
})
 
// 设置门的集合
let doors = []
// 加载汽车模型
const loader = new GLTFLoader()
const dracoLoader = new DRACOLoader()
dracoLoader.setDecoderPath("/draco/")
loader.setDRACOLoader(dracoLoader)
loader.load("/public/model/Lamborghini.glb",(gltf)=>{
  const carModel = gltf.scene
  carModel.rotation.y = Math.PI
  carModel.traverse((obj)=>{
    if(obj.name === 'Object_103' || obj.name === 'Object_64' || obj.name === 'Object_77'){
      // 车身
      obj.material = bodyMaterial
    }else if(obj.name === 'Object_90'){
      // 玻璃
      obj.material = glassMaterial
    }else if(obj.name === 'Empty001_16' || obj.name === 'Empty002_20'){
      // 门
      doors.push(obj)
    }else{
      return true
    }
    // 车模型阴影
    obj.castShadow = true
  })
  scene.add(gltf.scene)
})
 
// 设置环境光源
const ambientLight = new THREE.AmbientLight('#fff',0.5)
scene.add(ambientLight)
 
// 设置地板样式
const floorGeometry = new THREE.PlaneGeometry(20,20)
const floormaterial = new THREE.MeshPhysicalMaterial({
  side: THREE.DoubleSide,
  color: 0x808080,
  metalness: 0, // 设置金属度
  roughness: 0.1, // 设置粗糙度
  wireframe: false // 关闭网格线
})
const mesh = new THREE.Mesh(floorGeometry,floormaterial)
mesh.rotation.x = Math.PI / 2
// mesh.receiveShadow = true // 接收阴影
scene.add(mesh)
 
// 设置圆柱体模拟展厅
const cylinder = new THREE.CylinderGeometry(12,12,20,32)
const cylindermaterial = new THREE.MeshPhysicalMaterial({
  color: 0x6c6c6c,
  side: THREE.DoubleSide
})
const cylinderMesh = new THREE.Mesh(cylinder,cylindermaterial)
scene.add(cylinderMesh)
 
// 设置聚光灯(让汽车更具有立体金属感)
const spotLight = new THREE.SpotLight('#fff',2)
spotLight.angle = Math.PI / 8 // 散射角度,和水平线的夹角
spotLight.penumbra = 0.2 // 横向,聚光锥的半影衰减百分比
spotLight.decay = 2 // 纵向,沿着光照距离的衰减量
spotLight.distance = 30
spotLight.shadow.radius = 10
spotLight.shadow.mapSize.set(4096,4096)
spotLight.position.set(-5,10,1)
spotLight.target.position.set(0,0,0) // 光照射的方向
spotLight.castShadow = true
scene.add(spotLight)
 
// 设置gui模板控制
// 修改默认面板名称
gui.domElement.parentNode.querySelector('.title').textContent = '3D汽车动态操作'
 
const bodyChange = gui.addFolder("车身材质设置")
bodyChange.close() // 默认关闭状态
bodyChange.addColor(bodyMaterial,'color').name('车身颜色').onChange(value=>{
  bodyMaterial.color.set(value)
})
bodyChange.add(bodyMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  bodyMaterial.metalness = value
})
bodyChange.add(bodyMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  bodyMaterial.roughness = value
})
bodyChange.add(bodyMaterial,'clearcoat',0,1).name('清漆强度').onChange(value=>{
  bodyMaterial.clearcoat = value
})
bodyChange.add(bodyMaterial,'clearcoatRoughness',0,1).name('清漆层粗糙度').onChange(value=>{
  bodyMaterial.clearcoatRoughness = value
})
const glassChange = gui.addFolder("玻璃设置")
glassChange.close() // 默认关闭状态
glassChange.addColor(glassMaterial,'color').name('玻璃颜色').onChange(value=>{
  glassMaterial.color.set(value)
})
glassChange.add(glassMaterial,'metalness',0,1).name('金属度').onChange(value=>{
  glassMaterial.metalness = value
})
glassChange.add(glassMaterial,'roughness',0,1).name('粗糙度').onChange(value=>{
  glassMaterial.roughness = value
})
glassChange.add(glassMaterial,'transmission',0,1).name('透光性').onChange(value=>{
  glassMaterial.transmission = value
})
 
// 设置汽车状态
let carStatus;
// 打开左车门
const carLeftOpen = () => { 
  carStatus = 'open'
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[1])
}
// 打开右车门
const carRightOpen = () => { 
  carStatus = 'open'
  setAnimationDoor({ x: 0 }, { x: Math.PI / 3 }, doors[0])
}
// 关闭左车门
const carLeftClose = () => { 
  carStatus = 'close'
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[1])
}
// 关闭右车门
const carRightClose = () => { 
  carStatus = 'close'
  setAnimationDoor({ x: Math.PI / 3 }, { x: 0 }, doors[0])
}
 
// 车内视角
const carIn = () => {
  setAnimationCamera({ cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 }, { cx: -0.27, cy: 0.83, cz: 0.60, ox: 0, oy: 0.5, oz: -3 });
}
// 车外视角
const carOut = () => {
  setAnimationCamera({ cx: -0.27, cy: 0.83, cz: 0.6, ox: 0, oy: 0.5, oz: -3 }, { cx: 4.25, cy: 1.4, cz: -4.5, ox: 0, oy: 0.5, oz: 0 });
}
 
var obj = { carRightOpen,carLeftOpen,carRightClose,carLeftClose,carIn,carOut }
// 设置车身动态操作
const doChange = gui.addFolder("车身动态操作设置")
doChange.close() // 默认关闭状态
doChange.add(obj, "carLeftOpen").name('打开左车门')
doChange.add(obj, "carRightOpen").name('打开右车门')
doChange.add(obj, "carLeftClose").name('关闭左车门')
doChange.add(obj, "carRightClose").name('关闭右车门')
doChange.add(obj, "carIn").name('车内视角')
doChange.add(obj, "carOut").name('车外视角')
 
// 设置补间动画
const setAnimationDoor = (start, end, mesh) => {
  const tween = new TWEEN.Tween(start).to(end, 1000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
    mesh.rotation.x = that.x
  })
  tween.start()
}
const setAnimationCamera = (start, end) => {
  const tween = new TWEEN.Tween(start).to(end, 3000).easing(TWEEN.Easing.Quadratic.Out)
  tween.onUpdate((that) => {
      //  camera.postition  和 controls.target 一起使用
      camera.position.set(that.cx, that.cy, that.cz)
      controls.target.set(that.ox, that.oy, that.oz)
  })
  tween.start()
}
 
// 设置点击打开车门的动画效果
window.addEventListener('click', onPointClick);
function onPointClick(event) {
  let pointer = {}
  pointer.x = (event.clientX / window.innerWidth) * 2 - 1;
  pointer.y = - (event.clientY / window.innerHeight) * 2 + 1;
  var vector = new THREE.Vector2(pointer.x, pointer.y)
  var raycaster = new THREE.Raycaster()
  raycaster.setFromCamera(vector, camera)
  let intersects = raycaster.intersectObjects(scene.children);
  intersects.forEach((item) => {
    if (item.object.name === 'Object_64' || item.object.name === 'Object_77') {
      if (!carStatus || carStatus === 'close') {
        carLeftOpen()
        carRightOpen()
      } else {
        carLeftClose()
        carRightClose()
      }
    }
  })
}
 
// 创建聚光灯函数
const createSpotlight = (color) => {
  const newObj = new THREE.SpotLight(color, 2);
  newObj.castShadow = true;
  newObj.angle = Math.PI / 6;;
  newObj.penumbra = 0.2;
  newObj.decay = 2;
  newObj.distance = 50;
  return newObj;
}
 
// 设置图片背景
const spotLight1 = createSpotlight('#ffffff');
const texture = new THREE.TextureLoader().load('src/assets/imgs/奥特曼.jpg')
spotLight1.position.set(0, 3, 0);
spotLight1.target.position.set(-10, 3, 10)
spotLight1.map = texture
const lightHelper = new THREE.SpotLightHelper(spotLight1);
scene.add(spotLight1);
 
// 设置渲染函数
const render = (time) =>{ 
  controls.update()
  TWEEN.update(time)
  renderer.render(scene,camera)
  requestAnimationFrame(render)
}
render()
</script>
相关推荐
崔庆才丨静觅9 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606110 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了10 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅10 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅10 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅11 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment11 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅11 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊11 小时前
jwt介绍
前端
爱敲代码的小鱼11 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax