UNPKG

@damienmortini/three

Version:
244 lines (203 loc) 6.32 kB
import { Mesh, Object3D, PlaneGeometry, Texture, } from '../../../three/src/Three.js'; import THREEShaderMaterial from '../material/THREEShaderMaterial.js'; export default class THREEText extends Object3D { constructor({ textContent = '', font = '10px sans-serif', fillStyle = 'black', textAlign = 'start', shadowColor = 'rgba(0, 0, 0 ,0)', shadowBlur = 0, shadowOffsetX = 0, shadowOffsetY = 0, scale = 1, maxWidth = Infinity, geometry = new PlaneGeometry(1, 1), material = new THREEShaderMaterial({ type: 'basic', transparent: true, }), } = {}) { super(); this._scale = scale; this._canvas = document.createElement('canvas'); this._context = this._canvas.getContext('2d'); this._texture = new Texture(this._canvas); material.map = this._texture; this.textContent = textContent; this.font = font; this.fillStyle = fillStyle; this.textAlign = textAlign; this.maxWidth = maxWidth; this.shadowColor = shadowColor; this.shadowBlur = shadowBlur; this.shadowOffsetX = shadowOffsetX; this.shadowOffsetY = shadowOffsetY; this._mesh = new Mesh(geometry, material); this.add(this._mesh); this._update(); } _updateContextProperties() { this._context.font = this.font; this._context.fillStyle = this.fillStyle; this._context.shadowColor = this.shadowColor; this._context.shadowBlur = this.shadowBlur; this._context.shadowOffsetX = this.shadowOffsetX; this._context.shadowOffsetY = this.shadowOffsetY; this._context.textBaseline = 'top'; } _update() { if (!this._mesh) { return; } this._updateContextProperties(); const shadowOffsetX = this.shadowOffsetX - this.shadowBlur; const shadowOffsetY = this.shadowOffsetY - this.shadowBlur; const words = this.textContent.split(' '); const spaceWidth = this._context.measureText(' ').width; const wordsWidth = new Map(); const lines = [{ textContent: '', width: 0, }]; for (const word of words) { if (!wordsWidth.get(word)) { wordsWidth.set(word, this._context.measureText(word).width); } } let width = 0; let lineNumber = 0; for (const word of words) { const newWidth = lines[lineNumber].width + wordsWidth.get(word); if (newWidth > this.maxWidth) { lineNumber++; lines[lineNumber] = { textContent: word, width: wordsWidth.get(word), }; } else { if (lines[lineNumber].textContent !== '') { lines[lineNumber].textContent += ' '; } lines[lineNumber].textContent += word; lines[lineNumber].width += spaceWidth + wordsWidth.get(word); } width = Math.max(width, lines[lineNumber].width); } width += this.shadowBlur * 2 + Math.abs(this.shadowOffsetX); const lineHeight = parseFloat(/\b(\d*)px/.exec(this._context.font)[1]); let height = lineHeight; height *= lines.length; height += this.shadowBlur * 2 + Math.abs(this.shadowOffsetY); if (this._canvas.width !== width || this._canvas.height !== height) { this._canvas.width = width; this._canvas.height = height; this._updateContextProperties(); } this._mesh.position.y = -shadowOffsetY * 0.5 * this._scale; if (this.textAlign === 'start' || this.textAlign === 'left') { this._mesh.position.x = (this._canvas.width * 0.5 + Math.min(0, shadowOffsetX)) * this._scale; } else if (this.textAlign === 'end' || this.textAlign === 'right') { this._mesh.position.x = (-this._canvas.width * 0.5 + Math.max(0, shadowOffsetX)) * this._scale; } else { this._mesh.position.x = shadowOffsetX * 0.5 * this._scale; } this._mesh.scale.x = this._canvas.width * this._scale; this._mesh.scale.y = this._canvas.height * this._scale; this._context.globalAlpha = 1 / 255; this._context.fillRect(0, 0, width, height); this._context.globalAlpha = 1; for (const [i, line] of lines.entries()) { let offsetX; switch (this.textAlign) { case 'start': case 'left': offsetX = 0; break; case 'center': offsetX = (width - line.width) * 0.5; break; case 'end': case 'right': offsetX = width - line.width; break; } this._context.fillText(line.textContent, offsetX + (shadowOffsetX < 0 ? Math.abs(shadowOffsetX) : 0), (shadowOffsetY < 0 ? Math.abs(shadowOffsetY) : 0) + lineHeight * i); } this._texture.needsUpdate = true; } get material() { return this._mesh.material; } set textContent(value) { this._textContent = value; this._update(); } get textContent() { return this._textContent; } set font(value) { this._context.font = this._font = value; this._update(); } get font() { return this._font; } set fillStyle(value) { this._context.fillStyle = this._fillStyle = value; this._update(); } get fillStyle() { return this._fillStyle; } set textAlign(value) { this._textAlign = value; this._update(); } get textAlign() { return this._textAlign; } set maxWidth(value) { this._maxWidth = value; this._update(); } get maxWidth() { return this._maxWidth; } set shadowColor(value) { this._context.shadowColor = this._shadowColor = value; this._update(); } get shadowColor() { return this._shadowColor; } set shadowBlur(value) { this._context.shadowBlur = this._shadowBlur = value; this._update(); } get shadowBlur() { return this._shadowBlur; } set shadowOffsetX(value) { this._context.shadowOffsetX = this._shadowOffsetX = value; this._update(); } get shadowOffsetX() { return this._shadowOffsetX; } set shadowOffsetY(value) { this._context.shadowOffsetY = this._shadowOffsetY = value; this._update(); } get shadowOffsetY() { return this._shadowOffsetY; } }