前言:SVG作为一种优秀的矢量图形格式在Web得到广泛应用,three.js作为知名的WebGL库自然也对其提供了支持。然而,官方文档中对此的说明十分单薄,网上与此相关的资源也不多。经过多次试验之后,在此分享我的一点理解,包括SVGLoader,SVGObject,SVGRenderer,svg和THREE对象的互相转化等内容。

1 使用SVGLoader载入svg图像

常规显示svg图像,只需要在DOM中直接添加<svg>节点就行了,为何还要绕弯使用three.js的相关组件对其进行显示呢?本文中或许有答案。下面来看看如何实现吧。首先把依赖的文件都包含进来:

<script src="three.js/build/three.min.js"></script>
<script src="three.js/examples/js/renderers/SVGRenderer.js"></script>
<script src="three.js/examples/js/renderers/Projector.js"></script>
<script src="three.js/examples/js/loaders/SVGLoader.js"></script>

然后再正式干活。主要分2步:

  • 调用SVGLoader读取svg图像到内存
  • 使用SVGRenderer渲染svg对象

下面来分别对这两步进行说明

1.1 读取svg图像

参见官方文档

首先,SVGLoader的源码就几行,看不出背后的内容;其次,官方文档只是简单介绍了SVGLoader的属性和方法,没有案例。至于官方关于svg的只有SVGRenderer几个案例,其所渲染的图形数据却并不是svg图像,而是THREE的图形对象——这样就没什么参考意义了。

js svg怎么转换为element图标 svg转图片js_svg

SVGLoader的构造函数只有一个参数manager,并且有默认值为THREE.DefaultLoadingManager。这个manager的主要作用不是处理数据,而是在loader处理数据的过程中显示信息。因此除非你有要定制的信息,不然使用默认值就好。

然后看SVGLoader的方法,只有一个.load( url, onLoad, onProgress, onError )。4个参数中,后边两个是载入中和出错时要调用的函数,不是很重要,一般就用无名函数在控制台输出信息,也可以省略。前两个参数比较重要:

  • url:要载入的svg的url,字符串类型。必须,可以是本地路径(chrome无法读取本地文件的解决方案见1,或者换Firefox吧);
  • onLoad:载入完成时候调用的函数。此函数必须有一个参数,这个参数将会是一个SVG Document,当然是可以直接嵌入DOM的。例如下面的代码可以直接将svg放入DOM中显示。然而这样操作的话,如果url是超链接不如直接在DOM下面添加,如果是本地文件不如直接用js读,多此一举没什么意义,我只是举个栗子~
//此函数作为onLoad被传入.load
function(svgObj){
    //.load之后要载入的svg就被放入了svgObj中
    document.body.appenChild(svgObj);
}

使用SVGLoader载入svg文件完整的代码如下(此代码源自2

var svgManager = new THREE.SVGLoader();
//var url = 'https://upload.wikimedia.org/wikipedia/commons/b/b0/NewTux.svg';
var url = 'tst.svg';

function svg_loading_done_callback(doc) {
  init(new THREE.SVGObject(doc));
  render();
};

svgManager.load(url, 
                svg_loading_done_callback, 
                function(){console.log("Loading SVG...");},
                function(){console.log("Error loading SVG!");
                });

.load将svg图像读取到doc变量。要注意的是在回调函数init()的参数部分,SVG Document类型的doc变量被传入了THREE.SVGObject()构造函数的参数中,从而将svg对象变为了THREE.SVGObject。(控制台输出了这个对象的信息,发现它typeObject3D,但有一些问题,下文再说)

1.2 在SVGRenderer中显示svg

three.js提供了好几种renderer,比如WebGLRendererSVGRendererCanvasRendererCSS3DRenderer等。如果使用WebGLRenderer渲染svg显然不是一个好选择,因为要涉及到Shader相关的内容,实现起来会很复杂;而用CanvasRenderer去渲染svg显然是费力不讨好的事,丢掉了svg的特性。CSS3DRenderer渲染svg图像在放大时可能会出现锯齿3,所以SVGRenderer看起来是坠吼的选择。这里先用SVGRenderer渲染svg图像(代码同样修改自2),其具体的特性和CanvasRenderer后文再说。

var camera, scene, renderer;

function init(svgObject) {
    camera = new THREE.PerspectiveCamera(75, window.innerWidth window.innerHeight, 1, 10000);
    camera.position.z = 500;
    scene = new THREE.Scene();

    //set svgObject transform ---- invalid :(
    //svgObject.position.x = 10;
    //svgObject.rotation.z += 90/180*Math.PI;

    console.log(svgObject);
    scene.add(svgObject);

    renderer = new THREE.SVGRenderer();
    renderer.setClearColor(0xf0f0f0);
    renderer.setSize(window.innerWidth, window.innerHeight);
    document.body.appendChild(renderer.domElement);

    window.addEventListener('resize', onWindowResize, false);
}

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

function render() {
    renderer.render(scene, camera);
}

上一小节最后提到SVGObject有些问题:虽然它是Object3D类型,但是使用修改诸如.rotation.z等属性的方法进行旋转等空间变换操作只是改变了相应属性的值,并没有真的应用到svg图像的空间变换上!这就麻烦了,即使作为子物体放到group里,修改group的变换也没用,这个是SVGRenderer解析SVGObject的固有机制导致的,咱们下文再讲。

既然不能通过Object3D的方法修改svg的空间变换,那导进来的意义就不大了啊……svg最蛋疼的就是原生的空间变换中心是左上角(0,0)点,想以自身中心为基准变换要么先平移后变换再平移,这样非常麻烦;要么用CSS进行变换,也不是十分方便。现在three的变换这条路也给断了,sigh(然而后边还有一些补救的方法,咱们以后再讲)

1.3 参考资料

2 https://stackoverflow.com/questions/33140342/how-to-load-svg-file-into-svgrenderer-in-three-js
3 https://github.com/mrdoob/three.js/issues/4429#issuecomment-41985025