目前Web实现矢量渲染的主流技术包括SVG、VML和WebGL。相对而言,VML是一种较古老的技术,虽然未成为W3C标准,但被早期的IE浏览器(IE9如下)和微软Office普遍使用,目前已经远离了浏览器战场。因此可供选择的仅剩SVG和WebGL。SVG是XML的一个子集,秉承了一个标签对应一条数据的原则,目前常常被使用于数据量较小的web项目,好比图表和地铁图。Web矢量地图的数据量很是庞大,举个例子,以下图所示的一个512px*512px的瓦片,其数据量是一个接近5位数的二维数组。而这个瓦片仅仅是最简单的大陆和海洋轮廓,同尺寸街道图的数据量更加庞大。
javascript
处理庞大的数据量必然对性能的要求很是苛刻,何况因为中间隔着一层浏览器,Web地图并不能彻底发挥CPU的计算能力。在有限的CPU资源下若是可以借助其余计算资源则必事半功倍,可以调用GPU资源的WebGL便成为了惟一的选择。html
SVG不适合开发Web矢量地图的缘由主要有两点:前端
肯定了底层技术-WebGL以后,接下来须要选择合适的辅助技术,针对目标有两点:java
WebGL渲染与CSS无关,因此CSS开发框架的选型对总体的影响微乎其微,在此略过。node
WebGL能够理解为OpenGL在浏览器环境下的变种,保留了OpenGL ES的语义和规范,提供相对简洁的JavaScript API。绝大部分的shader能够实现WebGL和OpenGL的共用。开发WebGL shader的语言GLSL是一种语法接近C的强类型编程语言。这一点对于习惯了JavaScript的前端开发者们须要必定的调整。既然是调整,那么不妨调整的完全一些:将总体开发都引入强类型的概念。目前支持在JavaScript中引入强类型的主流框架有两种:TypeScript和Flow.js。TypeScript是JavaScript的强类型超集,Flow则更接近于一种类型注解或者注释工具。相对而言,引入Flow的成本更低,你能够自由决定哪些文件开启或者关闭类型检查,仅仅须要在文件顶部添加一行注释:webpack
// @flow
因此Flow很是适合现有的项目进行迁移,而若是使用TypeScript则更须要将所有源代码进行改写。好在目前要作的项目并无历史包袱,因此Flow的这点优点并不能做为技术选型的决定性因素。git
最终选择TypeScript的缘由有如下几点:es6
ES6正式推出了Typed Array标准,但其实早在ES6以前,支持WebGL的浏览器就已经提供了强类型数组的API,目的是为了提升计算性能。github
构建工具的选择相对比较多,Webpack、Rollup、gulp都是很是优秀的工具。最终选择Webpack的缘由很是简单:比较熟。web
Webpack的配置与常规的web项目大致相同,须要注意的两点是:
TypeScript自己支持编译为ES5或ES3,即将tsconfig.json
的编译选型target
修改成"es5"/"es3"
:
{ "compilerOptions": { "target": "es5" } }
TypeScript编译器对于语法规范的转译功能能够知足绝大多数ES6新功能,可是其功能的全面性相比较Babel仍然有些不足,因此为了对编译进行更精准的控制,项目中采用的方案是将TypeScript首先转译为ES6语法,再借助Babel将其转译为ES5,即将tsconfig.json
中的compilerOptions.target
设置为"es6"
,webpack配置以下:
module: { rules: [{ test: /\.ts$/, exclude: /node_modules/, use: ['babel-loader','awesome-typescript-loader'] } }
Webpack编译TypeScript的loader有两个:ts-loader
和awesome-typescript-loader
。最终选择后者的缘由固然不是由于它的名字中有个awesome
,而是相对于前者,awesome-typescript-loader
可以提供一些更加便利的功能,好比alias-别名。
若是源码的目录结构比较复杂,引用一个模块时可能须要写很长的路径名称,好比:
import Utils from '../../../utils';
为了令代码具备更好的易读性,咱们一般借助一些工具将模块的引用设置较短的别名。Webpack也有此功能,经过resolve
配置模块的别名:
resolve: { alias: { 'utils': path.resolve(__dirname,'src/utils') } }
但遗憾的是ts-loader
和awesome-typescript-loader
并不能直接使用Webpack的alias配置,源码中直接使用模块别名将会抛出not found
错误,请注意这个错误是TypeScript编译器抛出而非Webpack。解决方案很简单:在tsconfig.json
中配置模块别名。以下:
{ "paths": { "utils/*": ["src/utils/*"] } }
但这并非最终的解决方案,由于若是使用ts-loader
做为Webpack集成的话,Webpack并不能获取tsconfig.json
的别名配置,也就是说,Webpack将会抛出not found
错误。awesome-typescript-loader
很好地解决了这个问题,它能够将tsconfig.json
的别名配置映射至Webpack的resolve.alias
。固然,若是你仍然坚持使用ts-loader
也能够解决,若是你不怕麻烦的话:在Webpack中手动配置一样的resolve.alias
。
另外须要注意的是,使用awesome-typescript-loader
须要在Webpack的resolve
中建立对应的插件:
const TsConfigPathsPlugin = require('awesome-typescript-loader').TsConfigPathsPlugin; module.exports = { module: { rules: [{ test: /\.ts$/, exclude: /node_modules/, use: ['babel-loader','awesome-typescript-loader'] }, // other rules ] }, resolve: { plugins: [ new TsConfigPathsPlugin({ configFileName: Path.resolve(__dirname,'../tsconfig.json') }) ] } }
WebGL建立shader的流程为:
示例代码以下:
const source = ` precision mediump float; attribute vec2 a_pos; uniform vec4 u_color; uniform vec2 u_resolution; uniform vec2 u_translate; varying vec4 v_color; void main() { vec2 real_poistion = (a_pos+u_translate) / u_resolution * 2.0 - 1.0; gl_Position = vec4(real_poistion * vec2(1, 1), 0, 1); v_color = u_color; }`; // 建立shader实例 const Shader = gl.createShader(gl.VERTEX_SHADER); // 绑定shader源码 gl.shaderSource(Shader,source); // 编译 gl.compileShader(Shader);
shader的源码以字符串的形式绑定至shader实例,也就是说,不论shader的源码是用什么编程语言编写(好比能够按照上述代码中用JavaScript字符串编写,也能够直接用glsl语言编写),必定要保证以字符串的形式引入shader源码模块。秉承这项原则,最简单的shader构建方案即是上述代码中的字符串形式。好比将上述示例代码中的shader源码单独抽离为vertex.js
以下:
export default ` precision mediump float; attribute vec2 a_pos; uniform vec4 u_color; uniform vec2 u_resolution; uniform vec2 u_translate; varying vec4 v_color; void main() { vec2 real_poistion = (a_pos+u_translate) / u_resolution * 2.0 - 1.0; gl_Position = vec4(real_poistion * vec2(1, 1), 0, 1); v_color = u_color; }`;
而后在主文件中引入:
import VertexShaderSource from './vertex.js'; // 建立shader实例 const Shader = gl.createShader(gl.VERTEX_SHADER); // 绑定shader源码 gl.shaderSource(Shader,VertexShaderSource); // 编译 gl.compileShader(Shader);
这种书写方式优势是不须要对Webpack进行任何配置,可是却等于放弃了IDE对glsl语法的高亮、纠错等辅助功能。若是shader源码只有几行倒也没什么大问题,可是对于几十上百行的代码若是没有高亮辅助的话可能会严重影响开发效率。
解决这个问题的办法要从两方面入手:
第一个问题很好解决,由于咱们的目的是把glsl模块引入到js模块中而且做为字符串使用,因此Webpack要作的就是将glsl源码构建为字符串便可:
{ test: /\.glsl$/, loader: 'raw-loader' }
raw-loader
的功能是将被引入的文件内容转换为字符串。
除了强类型带来的开发模式转变之外,TypeScript最大的问题是不能自动识别ts之外的任何其余类型模块,即便最广泛的JSON也不行。好比下述代码在TypeScript环境下会报not found
错误:
import Data from '../data.json';
这时候须要用到TypeScript的声明文件。声明文件的做用简单来讲就是告知TypeScript编译器一些必要的信息以便被正确识别。好比声明一些全局的类型(type)、接口(interface)、模块(module)等。
默认状况下,TypeScript编译器会自动识别源码和node_modules
目录中@types
文件夹内的声明文件,你也能够经过配置tsconfig.json
中compilerOptions.typeRoots
指定声明文件目录。针对上文提到的TypeScript不识别glsl和json模块问题,咱们在源码目录的@types
文件夹中建立声明文件global.d.ts
,内容以下:
declare module '*.glsl'; declare module '*.json'; declare type WidthAndHeight = { width: number; height: number; };
上述代码中声明了三个信息:
glsl
后缀类型的文件为可识别模块;json
后缀类型的文件为可识别模块;WidthAndHeight
,此类型将在任何源码文件中直接使用。在以上配置的基础上还有一个注意事项:与ES6 modules不一样的是,TypeScript引入declare
声明的非ts模块并不能将其内容自动转化为默认导出,即export default
。好比在ES6环境下引入一个json文件:
import JsonData from './data.json';
而在TypeScript环境下须要使用如下语法:
import * as JsonData from './data.json';
具体代码能够参考demo:https://github.com/ihardcoder/demo_ts-webgl-webpack