UNPKG

@pmndrs/uikit

Version:

Build performant 3D user interfaces with Three.js and yoga.

88 lines (87 loc) 3.08 kB
import { Material, Mesh, MeshBasicMaterial, ShapeGeometry, Vector3 } from 'three'; import { Content } from './content.js'; import { computed, signal } from '@preact/signals-core'; import { abortableEffect, loadResourceWithParams } from '../utils.js'; import { SVGLoader } from 'three/examples/jsm/loaders/SVGLoader.js'; export class Svg extends Content { constructor(inputProperties, initialClasses, config) { const boundingBox = signal(undefined); super(inputProperties, initialClasses, { ...config, remeasureOnChildrenChange: false, depthWriteDefault: false, supportFillProperty: true, boundingBox, }); const svgResult = signal(undefined); loadResourceWithParams(svgResult, loadSvg, disposeSvg, this.abortSignal, computed(() => ({ src: this.properties.value.src, content: this.properties.value.content, }))); abortableEffect(() => { const result = svgResult.value; boundingBox.value = result?.boundingBox; if (result == null || result.meshes.length === 0) { this.notifyAncestorsChanged(); return; } super.add(...result.meshes); this.notifyAncestorsChanged(); return () => { super.remove(...result.meshes); }; }, this.abortSignal); } } const loader = new SVGLoader(); const svgCache = new Map(); async function loadSvg({ src, content, }) { if (src == null && content == null) { return undefined; } let result; if (src != null) { let promise = svgCache.get(src); if (promise == null) { svgCache.set(src, (promise = loader.loadAsync(src))); } result = (await promise); } else { result = loader.parse(content); } const meshes = []; for (const path of result.paths) { const shapes = SVGLoader.createShapes(path); const material = new MeshBasicMaterial({ color: path.color, toneMapped: false }); for (const shape of shapes) { const mesh = new Mesh(new ShapeGeometry(shape), material); mesh.matrixAutoUpdate = false; mesh.scale.y = -1; mesh.updateMatrix(); meshes.push(mesh); } } let boundingBox; const viewBoxNumbers = result.xml .getAttribute('viewBox') ?.split(/\s+/) .map((s) => Number.parseFloat(s)) .filter((value) => !isNaN(value)); if (viewBoxNumbers?.length === 4) { const [minX, minY, width, height] = viewBoxNumbers; boundingBox = { center: new Vector3(width / 2 + minX, -height / 2 - minY, 0), size: new Vector3(width, height, 0.00001), }; } return { meshes, boundingBox }; } function disposeSvg(result) { result?.meshes.forEach((mesh) => { if (mesh.material instanceof Material) { mesh.material.dispose(); } mesh.geometry.dispose(); }); }