使用Three.js加载TTF字体并实现键盘输入字符功能

Three.js是一个强大的JavaScript库,用于创建和显示3D计算机图形。它使得在浏览器中实现复杂的3D场景变得相对简单。本文将详细介绍如何使用Three.js加载TTF(TrueType Font)格式的字体,并实现键盘输入字符的功能。我们将通过一个具体的示例来展示这一过程,并提炼出核心代码,帮助读者更好地理解和应用。

项目概述

我们将创建一个网页应用,该应用使用Three.js加载TTF格式的字体,并允许用户通过键盘输入字符。这些字符将以3D形式显示在网页上,并且可以通过鼠标拖动来旋转视角。

项目结构

首先,我们需要一个基本的HTML结构来容纳我们的Three.js场景。以下是完整的HTML代码:

<!DOCTYPE html>
<html lang="en">
<head>
    <title>three.js webgl - loader - ttf</title>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
    <link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
    <div id="info"></div>
    <script type="importmap">
        {
            "imports": {
                "three": "../build/three.module.js",
                "three/addons/": "./jsm/"
            }
        }
    </script>
    <script type="module">
        import * as THREE from 'three';
        import { TTFLoader } from 'three/addons/loaders/TTFLoader.js';
        import { Font } from 'three/addons/loaders/FontLoader.js';
        import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';

        let container;
        let camera, cameraTarget, scene, renderer;
        let group, textMesh1, textMesh2, textGeo, material;
        let firstLetter = true;

        let text = 'three.js';
        const height = 20,
            size = 70,
            hover = 30,
            curveSegments = 4,
            bevelThickness = 2,
            bevelSize = 1.5;

        let font = null;
        const mirror = true;

        let targetRotation = 0;
        let targetRotationOnPointerDown = 0;

        let pointerX = 0;
        let pointerXOnPointerDown = 0;

        let windowHalfX = window.innerWidth / 2;

        init();
        animate();

        function init() {
            container = document.createElement('div');
            document.body.appendChild(container);

            // CAMERA
            camera = new THREE.PerspectiveCamera(30, window.innerWidth / window.innerHeight, 1, 1500);
            camera.position.set(0, 400, 700);
            cameraTarget = new THREE.Vector3(0, 150, 0);

            // SCENE
            scene = new THREE.Scene();
            scene.background = new THREE.Color(0x000000);
            scene.fog = new THREE.Fog(0x000000, 250, 1400);

            // LIGHTS
            const dirLight1 = new THREE.DirectionalLight(0xffffff, 0.4);
            dirLight1.position.set(0, 0, 1).normalize();
            scene.add(dirLight1);

            const dirLight2 = new THREE.DirectionalLight(0xffffff, 2);
            dirLight2.position.set(0, hover, 10).normalize();
            dirLight2.color.setHSL(Math.random(), 1, 0.5, THREE.SRGBColorSpace);
            scene.add(dirLight2);

            material = new THREE.MeshPhongMaterial({ color: 0xffffff, flatShading: true });

            group = new THREE.Group();
            group.position.y = 100;
            scene.add(group);

            const loader = new TTFLoader();
            loader.load('fonts/ttf/kenpixel.ttf', function (json) {
                font = new Font(json);
                createText();
            });

            const plane = new THREE.Mesh(
                new THREE.PlaneGeometry(10000, 10000),
                new THREE.MeshBasicMaterial({ color: 0xffffff, opacity: 0.5, transparent: true })
            );
            plane.position.y = 100;
            plane.rotation.x = - Math.PI / 2;
            scene.add(plane);

            // RENDERER
            renderer = new THREE.WebGLRenderer({ antialias: true });
            renderer.setPixelRatio(window.devicePixelRatio);
            renderer.setSize(window.innerWidth, window.innerHeight);
            container.appendChild(renderer.domElement);

            // EVENTS
            container.style.touchAction = 'none';
            container.addEventListener('pointerdown', onPointerDown);
            document.addEventListener('keypress', onDocumentKeyPress);
            document.addEventListener('keydown', onDocumentKeyDown);
            window.addEventListener('resize', onWindowResize);
        }

        function onWindowResize() {
            windowHalfX = window.innerWidth / 2;
            camera.aspect = window.innerWidth / window.innerHeight;
            camera.updateProjectionMatrix();
            renderer.setSize(window.innerWidth, window.innerHeight);
        }

        function onDocumentKeyDown(event) {
            if (firstLetter) {
                firstLetter = false;
                text = '';
            }
            const keyCode = event.keyCode;
            // backspace
            if (keyCode === 8) {
                event.preventDefault();
                text = text.substring(0, text.length - 1);
                refreshText();
                return false;
            }
        }

        function onDocumentKeyPress(event) {
            const keyCode = event.which;
            // backspace
            if (keyCode === 8) {
                event.preventDefault();
            } else {
                const ch = String.fromCharCode(keyCode);
                text += ch;
                refreshText();
            }
        }

        function createText() {
            textGeo = new TextGeometry(text, {
                font: font,
                size: size,
                height: height,
                curveSegments: curveSegments,
                bevelThickness: bevelThickness,
                bevelSize: bevelSize,
                bevelEnabled: true
            });
            textGeo.computeBoundingBox();
            textGeo.computeVertexNormals();
            const centerOffset = - 0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
            textMesh1 = new THREE.Mesh(textGeo, material);
            textMesh1.position.x = centerOffset;
            textMesh1.position.y = hover;
            textMesh1.position.z = 0;
            textMesh1.rotation.x = 0;
            textMesh1.rotation.y = Math.PI * 2;
            group.add(textMesh1);
            if (mirror) {
                textMesh2 = new THREE.Mesh(textGeo, material);
                textMesh2.position.x = centerOffset;
                textMesh2.position.y = - hover;
                textMesh2.position.z = height;
                textMesh2.rotation.x = Math.PI;
                textMesh2.rotation.y = Math.PI * 2;
                group.add(textMesh2);
            }
        }

        function refreshText() {
            group.remove(textMesh1);
            if (mirror) group.remove(textMesh2);
            if (!text) return;
            createText();
        }

        function onPointerDown(event) {
            if (event.isPrimary === false) return;
            pointerXOnPointerDown = event.clientX - windowHalfX;
            targetRotationOnPointerDown = targetRotation;
            document.addEventListener('pointermove', onPointerMove);
            document.addEventListener('pointerup', onPointerUp);
        }

        function onPointerMove(event) {
            if (event.isPrimary === false) return;
            pointerX = event.clientX - windowHalfX;
            targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
        }

        function onPointerUp() {
            if (event.isPrimary === false) return;
            document.removeEventListener('pointermove', onPointerMove);
            document.removeEventListener('pointerup', onPointerUp);
        }

        function animate() {
            requestAnimationFrame(animate);
            group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
            camera.lookAt(cameraTarget);
            renderer.render(scene, camera);
        }
    </script>
</body>
</html>

加载TTF字体

使用TTFLoader加载TTF字体,并将其转换为Three.js可以使用的字体格式。

const loader = new TTFLoader();
loader.load('fonts/ttf/kenpixel.ttf', function (json) {
    font = new Font(json);
    createText();
});

创建3D文本

使用加载的字体创建3D文本,并将其添加到场景中。

function createText() {
    textGeo = new TextGeometry(text, {
        font: font,
        size: size,
        height: height,
        curveSegments: curveSegments,
        bevelThickness: bevelThickness,
        bevelSize: bevelSize,
        bevelEnabled: true
    });
    textGeo.computeBoundingBox();
    textGeo.computeVertexNormals();
    const centerOffset = - 0.5 * (textGeo.boundingBox.max.x - textGeo.boundingBox.min.x);
    textMesh1 = new THREE.Mesh(textGeo, material);
    textMesh1.position.x = centerOffset;
    textMesh1.position.y = hover;
    textMesh1.position.z = 0;
    textMesh1.rotation.x = 0;
    textMesh1.rotation.y = Math.PI * 2;
    group.add(textMesh1);
    if (mirror) {
        textMesh2 = new THREE.Mesh(textGeo, material);
        textMesh2.position.x = centerOffset;
        textMesh2.position.y = - hover;
        textMesh2.position.z = height;
        textMesh2.rotation.x = Math.PI;
        textMesh2.rotation.y = Math.PI * 2;
        group.add(textMesh2);
    }
}

处理键盘输入

通过监听键盘事件,实现用户输入字符并实时更新3D文本。

function onDocumentKeyDown(event) {
    if (firstLetter) {
        firstLetter = false;
        text = '';
    }
    const keyCode = event.keyCode;
    // backspace
    if (keyCode === 8) {
        event.preventDefault();
        text = text.substring(0, text.length - 1);
        refreshText();
        return false;
    }
}

function onDocumentKeyPress(event) {
    const keyCode = event.which;
    // backspace
    if (keyCode === 8) {
        event.preventDefault();
    } else {
        const ch = String.fromCharCode(keyCode);
        text += ch;
        refreshText();
    }
}

function refreshText() {
    group.remove(textMesh1);
    if (mirror) group.remove(textMesh2);
    if (!text) return;
    createText();
}

更新文本

每当文本内容发生变化时,我们需要重新创建3D文本对象,并更新场景中的显示。

function refreshText() {
    group.remove(textMesh1);
    if (mirror) group.remove(textMesh2);

    if (!text) return;

    createText();
}

处理窗口大小变化

为了确保在窗口大小变化时,Three.js场景能够正确调整,我们需要监听窗口的resize事件,并更新相机和渲染器的参数。

function onWindowResize() {
    windowHalfX = window.innerWidth / 2;

    camera.aspect = window.innerWidth / window.innerHeight;
    camera.updateProjectionMatrix();

    renderer.setSize(window.innerWidth, window.innerHeight);
}

处理鼠标拖动

为了实现通过鼠标拖动来旋转视角的功能,我们需要监听鼠标的pointerdownpointermovepointerup事件。

function onPointerDown(event) {
    if (event.isPrimary === false) return;

    pointerXOnPointerDown = event.clientX - windowHalfX;
    targetRotationOnPointerDown = targetRotation;

    document.addEventListener('pointermove', onPointerMove);
    document.addEventListener('pointerup', onPointerUp);
}

function onPointerMove(event) {
    if (event.isPrimary === false) return;

    pointerX = event.clientX - windowHalfX;
    targetRotation = targetRotationOnPointerDown + (pointerX - pointerXOnPointerDown) * 0.02;
}

function onPointerUp() {
    if (event.isPrimary === false) return;

    document.removeEventListener('pointermove', onPointerMove);
    document.removeEventListener('pointerup', onPointerUp);
}

动画和渲染

通过requestAnimationFrame实现动画循环,并在每一帧中更新场景和渲染器。

function animate() {
    requestAnimationFrame(animate);
    group.rotation.y += (targetRotation - group.rotation.y) * 0.05;
    camera.lookAt(cameraTarget);
    renderer.render(scene, camera);
}

总结

通过本文的介绍,我们详细讲解了如何使用Three.js加载TTF字体并实现键盘输入字符的功能。我们从初始化Three.js场景开始,逐步讲解了加载TTF字体、创建3D文本、处理键盘输入、更新文本、处理窗口大小变化和鼠标拖动等核心步骤。希望这些内容能够帮助你更好地理解和应用Three.js进行3D文本的展示和交互。

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值