尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

Three.js 精灵文字教程

Three.js 精灵文字教程
📅 发布时间:2026/6/30 0:34:29

精灵文字 ·Sprite Text· ▶ 在线运行案例

  • 案例合集:三维可视化功能案例(threehub.cn)
  • 开源仓库github地址:https://github.com/z2586300277/three-cesium-examples
  • 400个案例代码:网盘链接

你将学到什么

  • OrbitControls 相机轨道交互
  • Canvas 动态纹理贴图
  • requestAnimationFrame渲染循环与resize自适应

效果说明

本案例演示精灵文字效果:用 Canvas 2D 绘制内容并实时映射为 Three.js 纹理;核心用到 OrbitControls、Canvas。建议先打开文首在线案例查看动态画面,再对照下方源码逐步理解。

核心概念

  • OrbitControls轨道旋转缩放;开enableDamping时每帧需controls.update()。

实现步骤

  • 搭建 Scene / Camera / Renderer 与 OrbitControls
  • rAF 循环中 update 并 render
  • 代码要点

    import * as THREE from 'three'

    import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js'

    const box = document.getElementById('box')

    const scene = new THREE.Scene()

    const camera = new THREE.PerspectiveCamera(75, box.clientWidth / box.clientHeight, 0.1, 1000)

    camera.position.set(0, 10, 10)

    const renderer = new THREE.WebGLRenderer()

    renderer.setSize(box.clientWidth, box.clientHeight)

    box.appendChild(renderer.domElement)

    new OrbitControls(camera, renderer.domElement)

    scene.add(new THREE.AmbientLight(0xffffff, 3), new THREE.AxesHelper(1000))

    animate()

    function animate() {

    requestAnimationFrame(animate)

    renderer.render(scene, camera)

    }

    window.onresize = () => {

    renderer.setSize(box.clientWidth, box.clientHeight)

    camera.aspect = box.clientWidth / box.clientHeight

    camera.updateProjectionMatrix()

    }

    const citys = await fetch('https://z2586300277.github.io/three-editor/dist/files/other/city.json').then(res => res.json()) // 获取城市数据

    const updateCanvasText = createCanvasText({ dpr: 1.4 }) // 创建canvas

    const getColor = () => '#' + Math.floor(Math.random() * 0xffffff).toString(16).padStart(6, '0') // 随机颜色

    for (const key in citys) {

    const canvas = updateCanvasText({ text: key, color: getColor() })

    const texture = new THREE.TextureLoader().load(canvas.toDataURL())

    const material = new THREE.SpriteMaterial({ map: texture })

    const sprite = new THREE.Sprite(material)

    sprite.scale.set(canvas.width / canvas.height, 1, 1)

    // 设置随机位置 sprite.position.set( Math.random() * 20 - 10, Math.random() * 20 - 10, Math.random() * 20 - 10 )

    scene.add(sprite)

    }

    // 创建canvas文字方法 function createCanvasText(params) {

    const defaultParams = { dpr: 1, maxWidth: 100, fontSize: 20, color: 'white', fontFamily: 'serif', align: 'center', border: false, ...params } // 默认参数

    const { dpr, border, maxWidth, fontSize, align } = defaultParams

    const devicePixelRatio = window.devicePixelRatio * dpr

    // 准备 cnvas const canvas = document.createElement('canvas')

    canvas.width = maxWidth * devicePixelRatio

    canvas.height = fontSize * devicePixelRatio

    // 获取 2d 上下文 const ctx = canvas.getContext('2d')

    ctx.imageSmoothingQuality = 'high'

    ctx.scale(devicePixelRatio, devicePixelRatio)

    // 创建边框 function createBorder() {

    ctx.strokeStyle = '#fff'

    // 创建宽度为10px的边框 ctx.lineWidth = 1 * devicePixelRatio;

    ctx.strokeRect(

    ctx.lineWidth / 2,

    ctx.lineWidth / 2,

    canvas.width / devicePixelRatio - ctx.lineWidth,

    canvas.height / devicePixelRatio - ctx.lineWidth

    )

    }

    // 创建文字 const createText = ({ text, color, fontSize, fontFamily }) => {

    // 参数设定 ctx.fillStyle = color || defaultParams.color

    ctx.font = fontSize || defaultParams.fontSize + 'px ' + fontFamily || defaultParams.fontFamily

    // 文本长度计算 let textMaxNum = 0

    let totalWidth = 0

    for (let i = 0; i < text.length; i++) {

    const metrics = ctx.measureText(text[i])

    totalWidth += metrics.width;

    if (totalWidth > maxWidth) break

    textMaxNum++

    }

    text = text.slice(0, textMaxNum)

    // 文字 绘制 const metrics = ctx.measureText(text) // 文本尺寸

    const actualHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent // 实际文字高度

    const textFillHeight = (canvas.height / devicePixelRatio - actualHeight) / 2 + metrics.actualBoundingBoxAscent

    let textLeftOffset = 0

    if (align === 'center') textLeftOffset = (canvas.width / devicePixelRatio - metrics.width) / 2

    ctx.fillText(text, textLeftOffset, textFillHeight, canvas.width / devicePixelRatio)

    }

    return (parameters) => {

    ctx.clearRect(0, 0, canvas.width, canvas.height) // 清空 canvas 文字

    if (border) createBorder() // 创建边框

    createText(parameters) // 创建文字

    return canvas

    }

    }

    完整源码:GitHub

    小结

    • 本文提供精灵文字完整 Three.js 源码与在线 Demo,建议先运行案例再改 uniform/参数做二次实验
    • 更多 Three.js 实战案例见 three-cesium-examples 合集 与 GitHub 开源仓库

相关新闻

  • 【题解-信息学奥赛一本通】1321:【例6.3】删数问题(Noip1994)
  • Minecraft世界区块管理神器:MCA Selector完全指南与实战技巧
  • 如何用ctfileGet实现城通网盘免等待下载:3个关键技术解析

最新新闻

  • 01 TCP 协议是流式协议
  • Netty 高性能网络编程:从零构建高并发服务器
  • 【TSP问题】基于帝企鹅算法AFO求解单仓库多旅行商问题MTSP附Matlab代码
  • 无需同看同一张图:跨被试神经表征对齐的VAE新范式
  • 只有 B 级能力的大模型,怎么干出 A 级的活?
  • 容器化 Java 应用 CPU 使用率监控口径解析:node exporter vs cAdvisor vs JMX

日新闻

  • 【计算机毕业设计案例】基于 Spring Boot+Vue 的电影售票系统设计与实现 前后端分离架构下影院在线购票管理平台(程序+文档+讲解+定制)
  • 到底 TMD 用哪个: npm, pnpm, Yarn, Bun, Deno? 傻瓜, 当然用 npm 啦
  • Google限制Meta使用Gemini模型 凸显AI授权竞争白热化

周新闻

  • Windows字体自定义终极方案:No!! MeiryoUI完全指南
  • Deepin Boot Maker:告别命令行,3分钟制作Linux启动盘的智能解决方案
  • Plain Craft Launcher 2:重新定义你的Minecraft游戏体验

月新闻

  • 【总结】入门篇:50句话让你记住架构核心概念
  • WeChatMsg技术方案解析:实现Mac微信数据自主管理的完整解决方案
  • WeChatMsg:革新性微信数据备份方案,打造你的专属数字记忆库

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号