实现地铁站监控,的交互式地铁线路图

简单的 canvas 翻角效果

2017/12/07 · HTML5 ·
Canvas

原文出处: 敖爽   

由于工作需求 ,
需要写一个翻角效果;图片 1

demo链接

右上角需要从无的状态撕开一个标记 , 且有动画过程 , 上图是实现的效果图 ,
不是gif

对这个翻角效果的难点在于没有翻开的时候露出的是dom下面的内容 ,
实现角度来说 纯dom + css动画的设计方案并没有相出一个好的对策 ;
于是捡起了好久之前学的入门级别的canvas;

下面说一下实现思路:

  1. 动画拆分 :
    将此动画分解成两部分 , 一部分是翻页出现的黑色三角区域 ,
    另一个是露出的橘色展示内容
    对于橘色的展示内容区域相对好一些 , 因为是一个规则图形 ,
    而黑色区域相对较难;

先从基础canvas使用方法说起 :

<div class=”container”> <canvas class=”myCanvas” width=”100″
height=”100″></canvas> </div>

1
2
3
<div class="container">
    <canvas class="myCanvas" width="100" height="100"></canvas>
</div>

布局如上 , 这里要说一点踩过的坑是 , canvas必须要设置上width 与 height ,
此处并非为css中的width与height;而是写在dom上的属性 ;
因为dom上的width与height标识了canvas的分辨率(个人理解);
所以此canvas画布分辨率为100*100 , 而展示尺寸是可以通过css控制;

js中首先要做的是获取canvas对象 ,

var canvas = document.querySelector(‘.myCanvas’); //获取canvas对应dom
var ctx = canvas.getContext(‘2d’); //此方法较为基础 ,
意为获取canvas绘画2d内容的工具(上下文) var cw = 100; //分辨率 ,
其实直接从dom上获取可能更好些 var ch = 100; //分辨率 ,
其实直接从dom上获取可能更好些

1
2
3
4
var canvas = document.querySelector(‘.myCanvas’); //获取canvas对应dom
var ctx = canvas.getContext(‘2d’); //此方法较为基础 , 意为获取canvas绘画2d内容的工具(上下文)
var cw = 100; //分辨率 , 其实直接从dom上获取可能更好些
var ch = 100; //分辨率 , 其实直接从dom上获取可能更好些

ctx这个绘画上下文在这个教程中起到的作用至关重要 ; 它提供了非常强大的api
, 比如用于画线 , 填充 , 写文字等 , 这样看来理解为画笔会更为简明一些;

此处效果需要用到的api如下 ( 不做详细解释 , 可w3c自行查询 );

ctx.save() //保存上下文状态 (比如画笔尺寸 颜色 旋转角度) ctx.restore()
//返回上次保存的上下文状态 ctx.moveTo(x,y) //上下文移动到具体位置
ctx.lineTo(x,y) //上下文以划线的形式移动到某位置 ctx.stroke() //
画线动作 ctx.quadraticCurveTo()
//上下文(画笔)按贝塞尔曲线移动(简单理解为可控的曲线即可) ctx.arc()
//画圆 ctx.beginPath() //开启新的画笔路径 ctx.closePath()
//关闭当前画笔路径 ctx.createLinearGradient() //创建canvas渐变对象
ctx.fill() //对闭合区域进行填充 ctx.globalCompositeOperation
//画笔的重叠模式

1
2
3
4
5
6
7
8
9
10
11
12
ctx.save() //保存上下文状态 (比如画笔尺寸 颜色 旋转角度)
ctx.restore() //返回上次保存的上下文状态
ctx.moveTo(x,y) //上下文移动到具体位置
ctx.lineTo(x,y) //上下文以划线的形式移动到某位置
ctx.stroke() // 画线动作
ctx.quadraticCurveTo() //上下文(画笔)按贝塞尔曲线移动(简单理解为可控的曲线即可)
ctx.arc() //画圆
ctx.beginPath() //开启新的画笔路径
ctx.closePath() //关闭当前画笔路径
ctx.createLinearGradient() //创建canvas渐变对象
ctx.fill() //对闭合区域进行填充
ctx.globalCompositeOperation //画笔的重叠模式

可能方法列举的不够详尽 , 见谅.

首先是绘制黑色翻出的部分 , 图形分解为如下几部分(请根据上图脑补)

  1. 左上角向右下的半弧 ╮
  2. 然后是竖直向下的竖线 |
  3. 然后是向右的半圆 ╰
  4. 再然后是向右的横线
  5. 接着还是向右下的半弧 ╮
  6. 最后是将线连接会起点

于是第一步 我们要先将画笔移动到 起始位置

ctx.moveTo(50,0);

1
ctx.moveTo(50,0);

然后

ctx.quadraticCurveTo(55 , 5 , 55 , 25); //
可以理解为从(50,0)这个点划线到(55,25)这个点 ,
中间会受到(55,5)这个点将直线想磁铁一样”吸”成曲线;

1
ctx.quadraticCurveTo(55 , 5 , 55 , 25); // 可以理解为从(50,0)这个点划线到(55,25)这个点 , 中间会受到(55,5)这个点将直线想磁铁一样"吸"成曲线;

于是第一个向右下的半弧完成 , 此时canvas上没有任何绘制内容 ,
因为还没有执行过绘制方法例如stroke或fill,

接下来直线向下就是简单的移动

ctx.lineTo(55 , 40);

1
ctx.lineTo(55 , 40);

这个时候我们接下来应该画向右的半圆 , 这个时候再用贝塞尔曲线绘制
实在有些不太合适 , 因为从图上来看 , 这里完全是1/4的圆 ,
所以要使用canvas提供的画圆的api

ctx.arc(60 , 40 , 5 , Math.PI , Math.PI / 2 , true);

1
ctx.arc(60 , 40 , 5 , Math.PI , Math.PI / 2 , true);

上述画圆的代码意为 : 以(60,40)点为圆心 , 5为半径 , 逆时针从
180度绘制到90度 , 180度就是圆心的水平向左 到达点(55,40) , 与上一步连接上
, 然后又因为屏幕向下为正 , 90度在圆心正下方 , 所以绘制出此半圆

于是按照相同的步骤 水平向右

ctx.lineTo(75 , 45);

1
ctx.lineTo(75 , 45);

然后再次使用贝塞尔曲线用第一步的思路画出向右下的弧;

ctx.quadraticCurveTo( 95 , 45 , 100 , 50 );

1
ctx.quadraticCurveTo( 95 , 45 , 100 , 50 );

同理 上述贝塞尔曲线可以理解为一条从( 75 , 45 ) 到 ( 100 , 50 )的线被 (
95 , 45 )”吸”成曲线

最后链接起点 , 闭合绘画区域

ctx.lineTo(50 , 0);

1
ctx.lineTo(50 , 0);

这个时候黑色区域的翻页就画完了 , 然后此时开始填充颜色 ;

var gradient = ctx.createLinearGradient(50 , 50 , 75 , 75);
gradient.addColorStop(0 , ‘#ccc’); gradient.addColorStop(0.7 ,
‘#111’); gradient.addColorStop(1 , ‘#000’);

1
2
3
4
var gradient = ctx.createLinearGradient(50 , 50 , 75 , 75);
gradient.addColorStop(0 , ‘#ccc’);
gradient.addColorStop(0.7 , ‘#111’);
gradient.addColorStop(1 , ‘#000’);

我们通过上述代码创建一个 从( 50 , 50 )点到(75 , 75)点的线性渐变 , 颜色从
#ccc 到 #111 到 #000 ; 创建高光效果;
然后填充:

ctx.fillStyle = gradient; ctx.fill();

1
2
ctx.fillStyle = gradient;
ctx.fill();

于是翻页效果的一半就算完成了。

至此 , 我要说一点我领悟的canvas的绘画”套路”;

对于上述教程中 , 有一步我们使用了一个词叫做 闭合 ,
闭合的概念在canvas中是真是存在的 , 对于fill方法来说
填充的区间是有一个空间尺寸才可以的 , 比如我们绘画的这个黑色的三角形 ,
加入我们最后没有将终点与起点相连接 ,
同样canvas会自动帮我们链接最后一笔绘画的位置到起点 , 强制行程闭合空间 ,
而这样我们想再多画几个新的闭合空间就麻烦了 , 所以canvas提供了如下api
新建闭合路径:

ctx.beginPath(); //新建路径 ctx.closePath(); //闭合路径

1
2
ctx.beginPath(); //新建路径
ctx.closePath(); //闭合路径

所以对于我们接下来要绘制右上角橘色区域来说 ,
我们在绘制黑色区域之前首先要做的是

ctx.beginPath(); …

1
2
ctx.beginPath();

然后在fill之前 我们应该

ctx.closePath();

1
ctx.closePath();

也就是说beginPath 到 closePath之间标识着我们自己的一个完整的绘画阶段.

那么接下来绘制右上角的橘色区域就简单很多了:

ctx.beginPath(); ctx.moveTo(50,0); ctx.lineTo(100,50);
ctx.lineTo(100,0); ctx.lineTo(50,0); ctx.closePath(); ctx.fillStyle =
‘#ff6600’; ctx.fill();

1
2
3
4
5
6
7
8
ctx.beginPath();
ctx.moveTo(50,0);
ctx.lineTo(100,50);
ctx.lineTo(100,0);
ctx.lineTo(50,0);
ctx.closePath();
ctx.fillStyle = ‘#ff6600’;
ctx.fill();

于是右上角的橘色区域我们就绘制完成了;

文字绘制

接下来绘制”new” , 实际上是使用canvas简单的文本绘制 , 代码如下:

var deg = Math.PI / 180; ctx.globalCompositeOperation = ‘source-atop’;
//canvas层叠模式 ctx.beginPath(); ctx.font = ’14px Arial’;
//设置字体大小 字体 ctx.textAlign = ‘center’; // 字体对齐方式
ctx.translate(78 , 22); // 移动canvas画布圆点 ctx.rotate(45 * deg); //
旋转画布 ctx.fillStyle = ‘#fff’; // 设置文字颜色 ctx.fillText(‘NEW’ , 0
, 0); //文字绘制动作 ctx.closePath();

1
2
3
4
5
6
7
8
9
10
var deg = Math.PI / 180;
ctx.globalCompositeOperation = ‘source-atop’; //canvas层叠模式
ctx.beginPath();
ctx.font = ’14px Arial’; //设置字体大小 字体
ctx.textAlign = ‘center’; // 字体对齐方式
ctx.translate(78 , 22);  // 移动canvas画布圆点
ctx.rotate(45 * deg);    // 旋转画布
ctx.fillStyle = ‘#fff’;  // 设置文字颜色
ctx.fillText(‘NEW’ , 0 , 0); //文字绘制动作
ctx.closePath();

对于上述代码中 , 文字的相关api是属于没有难度的 , 只是设置而已 ,
需要理解的部分在于 translate和rotate,

这两个方法中 translate的意思为移动canvas画布的( 0 , 0 )点到
(78,22),然后旋转45度, 再将文字渲染在原点 , 实际就是 ( 78 , 22 )
这个点上, 此时我们对canvas的画笔做出了非常大的修改

比如我们修改了旋转角度以及画布圆点 ,
这种操作或许只在我们需要绘制倾斜的new 的时候需要 ,
后期可能就不需要使用了 ,

还好canvas的画笔是存在”状态”的, 通过ctx.save();可以保存当前画笔的状态 ,
通过ctx.restore();可以恢复到上次画笔保存的状态.

于是我个人理解到 , 在开发canvas动画时 , 一个较好的习惯就是 ,
在beginPath之前先ctx.save();保存画笔状态 ,
在closePath后ctx.restore();恢复之前的画笔状态 ,
这样我们的每一个绘制阶段对于画笔的修改都将是不会有影响的.( 个人经验 )

ctx.globalCompositeOperation = ‘source-atop’; //canvas层叠模式

1
ctx.globalCompositeOperation = ‘source-atop’; //canvas层叠模式

代码中这部分是指 我们绘制的文字new 与 橘色三角形区域的重叠关系 ,
此方法取值较多 , 此处不做过多介绍 , source-atop值可以使重叠区域保留 ,
新绘制的内容在重叠区域以外的部分消失 , 以此达到new在里面的效果

到这里我们就开发好了翻角效果的完全展示的状态 ,
那么如何让这个区域动起来呢?

此处需要使用h5提供的用于刷帧的函数 requestAnimationFrame ;

此方法可简单理解为 16毫秒的定时器 ,
但是厉害的是可以再各个环境中自动匹配到可达到的相对顺畅的帧率 ,
实际并不是定时器哈~

我们需要在这个循环执行的函数中 , 将上述的绘制内容重复绘制 , 例如 :

function draw(){ drawMethod(); //绘制三角等内容
window.requestAnimationFrame(function(){ draw(); }) } function
drawMethod(){ //… }

1
2
3
4
5
6
7
8
9
function draw(){
    drawMethod(); //绘制三角等内容
    window.requestAnimationFrame(function(){
        draw();
    })
}
function drawMethod(){
    //…
}

这样我们就可以达到刷帧的效果了 ,
于是接着我们要做的就是控制绘制时各个数值的参数.

比如我们是以 (50,0)为起点 , ( 100 , 50
)为终点这样的两个移动点为绘制标记的 , 如果我们将两个点进行存储 ,
并且每次执行drawMethod的时候更新点的位置 , 然后清空canvas ,再绘制新的点
那么就可以达到canvas动起来的目的了;

实际效果链接在这里

在上面的demo链接中 , 自己定义了一个速度与加速度的关系 ,
比如每次绘制一次canvas后 , 将存储的点坐标进行增加一个speed值 ,
然后speed值也增加 , 这样speed对应的概念就是速度 ,
而speed的增加值对应的就是加速度. 所以就呈现了一种加速运动的状态;

以上内容纯属个人理解内容 , 若果有哪里理解错了 欢迎各位大大指点 ,
另demo链接失效可私信.

1 赞 1 收藏
评论

图片 2

基于 HTML5 Canvas 实现地铁站监控

2017/11/21 · HTML5 ·
Canvas

原文出处: hightopo   

伴随国内经济的高速发展,人们对安全的要求越来越高。为了防止下列情况的发生,您需要考虑安装安防系统:
提供证据与线索:很多工厂银行发生偷盗或者事故相关机关可以根据录像信息侦破案件,这个是非常重要的一个线索。还有一些纠纷或事故,也可以通过录像很容易找出相关人员的责任。
人防成本高:现在很多地方想到安全就想到要雇佣保安,每个保安每个月
800,每天 3 班倒,一班人员一年就需要将近 4
万元,相比于电子安防设备成本并不便宜,而且使用电子安防设备几年内就不太需要更换。所以人防成本相对也很高。人防辅助:多数情况下,完全靠人来保证安全是一件很困难的事情,很多事情需要电子保安器材(如监视器、报警器)辅助才更完美。特殊场合必须使用:在一些恶劣条件下(高热、寒冷、封闭等),人很难用肉眼观察清楚,或者环境根本不适合人的停留,必须使用电子安防设备。隐蔽性:使用电子安防设备,一般人不会感觉时时被监控,具有隐蔽性。24
小时安全保证:要达到 24
小时不间断的安全需要,电子设备是必须考虑的。远程监控:随着计算机技术与网络技术的发展,远程监控观看异地图象已经成为可能,现在已经有很多公司的负责人已经可以
INTERNET
及时观看世界各地的任何分公司情况,有利于及时了解情况。图象保存:数字录像技术的发展,使得影象可以通过计算机数字存储设备得以保存,可以保存时间更长,图象更清晰。生产管理:管理人员可以及时、直观的了解生产第一线的情况,便于指挥与管理。

鉴于监控系统在国内的需求量较大,对于大范围的监控,如地铁站,更是需要监控系统来防止意外的发生,今天我们给大家介绍一下如何创建一个地铁站监控系统的前端部分。

http://www.hightopo.com/demo/…
进入页面右键“审查元素”可查看例子源代码。

本例的动态效果如下:图片 3

我们先来搭建基础场景,在 HT
中,非常常用的一种方法来将外部场景导入到内部就是靠解析 JSON 文件,用
JSON 文件来搭建场景的好处之一就是可以循环利用,我们今天的场景就是利用
JSON 画出来的。接下来 HT 将利用 ht.Default.xhrLoad 函数载入 JSON
场景,并用 HT 封装的 DataModel.deserialize(json)
来反序列化,并将反序列化的对象加入
DataModel:

ht.Default.xhrLoad(‘demo2.json’, function(text) { var json =
ht.Default.parse(text); if(json.title) document.title = json.title;//将
JSON 文件中的 titile 赋给全局变量 titile
dataModel.deserialize(json);//反序列化
graphView.fitContent(true);//缩放平移拓扑以展示所有图元,即让所有的元素都显示出来
});

1
2
3
4
5
6
ht.Default.xhrLoad(‘demo2.json’, function(text) {
    var json = ht.Default.parse(text);
    if(json.title) document.title = json.title;//将 JSON 文件中的 titile 赋给全局变量 titile
    dataModel.deserialize(json);//反序列化
    graphView.fitContent(true);//缩放平移拓扑以展示所有图元,即让所有的元素都显示出来
});

在 HT 中,Data 类型对象构造时内部会自动被赋予一个 id 属性,可通过
data.getId() 和 data.setId(id) 获取和设置,Data 对象添加到 DataModel
之后不允许修改 id 值,可通过 dataModel.getDataById(id) 快速查找 Data
对象。一般建议 id 属性由 HT 自动分配,用户业务意义的唯一标示可存在 tag
属性上,通过 Data#setTag(tag) 函数允许任意动态改变 tag
值,通过DataModel#getDataByTag(tag) 可查找到对应的 Data
对象,并支持通过 DataModel#removeDataByTag(tag) 删除 Data
对象。我们这边通过在 JSON 中设置 Data 对象的 tag 属性,在代码中通过
dataModel.getDataByTag(tag) 函数来获取该 Data 对象:

var fan1 = dataModel.getDataByTag(‘fan1’); var fan2 =
dataModel.getDataByTag(‘fan2’); var camera1 =
dataModel.getDataByTag(‘camera1’); var camera2 =
dataModel.getDataByTag(‘camera2’); var camera3 =
dataModel.getDataByTag(‘camera3’); var redAlarm =
dataModel.getDataByTag(‘redAlarm’); var yellowAlarm =
dataModel.getDataByTag(‘yellowAlarm’);

1
2
3
4
5
6
7
var fan1 = dataModel.getDataByTag(‘fan1’);
var fan2 = dataModel.getDataByTag(‘fan2’);
var camera1 = dataModel.getDataByTag(‘camera1’);
var camera2 = dataModel.getDataByTag(‘camera2’);
var camera3 = dataModel.getDataByTag(‘camera3’);
var redAlarm = dataModel.getDataByTag(‘redAlarm’);
var yellowAlarm = dataModel.getDataByTag(‘yellowAlarm’);

我在下图中做了各标签对应的元素:图片 4

接着我们对需要旋转、闪烁的对象进行设置,HT 中对“旋转”封装了
setRotation(rotation)
函数,通过获得对象当前的旋转角度,在这个角度的基础上再增加某个弧度,通过
setInterval 定时调用,这样就能在一定的时间间隔内旋转相同的弧度:

JavaScript

setInterval(function(){ var time = new Date().getTime(); var deltaTime =
time – lastTime; var deltaRotation = deltaTime * Math.PI / 180 * 0.1;
lastTime = time; fan1.setRotation(fan1.getRotation() +
deltaRotation*3); fan2.setRotation(fan2.getRotation() +
deltaRotation*3); camera1.setRotation(camera1.getRotation() +
deltaRotation/3); camera2.setRotation(camera2.getRotation() +
deltaRotation/3); camera3.setRotation(camera3.getRotation() +
deltaRotation/3); if (time – stairTime > 500) { stairIndex–; if
(stairIndex < 0) { stairIndex = 8; } stairTime = time; } for (var i =
0; i < 8; i++) {//因为有一些相似的元素我们设置的 tag
名类似,只是在后面换成了1、2、3,所以我们通过 for 循环来获取 var color =
stairIndex === i ? ‘#F6A623’ : ‘#CFCFCF’;
dataModel.getDataByTag(‘stair_1_’ + i).s(‘shape.border.color’, color);
dataModel.getDataByTag(‘stair_2_’ + i).s(‘shape.border.color’, color);
} if (new Date().getSeconds() % 2 === 1) {
yellowAlarm.s(‘shape.background’, null); redAlarm.s(‘shape.background’,
null); } else { yellowAlarm.s(‘shape.background’, ‘yellow’);
redAlarm.s(‘shape.background’, ‘red’); } }, 5);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
setInterval(function(){
    var time = new Date().getTime();
    var deltaTime = time – lastTime;
    var deltaRotation = deltaTime * Math.PI / 180 * 0.1;
    lastTime = time;
 
    fan1.setRotation(fan1.getRotation() + deltaRotation*3);
    fan2.setRotation(fan2.getRotation() + deltaRotation*3);
    camera1.setRotation(camera1.getRotation() + deltaRotation/3);
    camera2.setRotation(camera2.getRotation() + deltaRotation/3);
    camera3.setRotation(camera3.getRotation() + deltaRotation/3);
 
    if (time – stairTime > 500) {
        stairIndex–;
        if (stairIndex < 0) {
            stairIndex = 8;
        }
        stairTime = time;
    }
 
    for (var i = 0; i < 8; i++) {//因为有一些相似的元素我们设置的 tag 名类似,只是在后面换成了1、2、3,所以我们通过 for 循环来获取
        var color = stairIndex === i ? ‘#F6A623’ : ‘#CFCFCF’;
        dataModel.getDataByTag(‘stair_1_’ + i).s(‘shape.border.color’, color);
        dataModel.getDataByTag(‘stair_2_’ + i).s(‘shape.border.color’, color);
    }
 
    if (new Date().getSeconds() % 2 === 1) {
        yellowAlarm.s(‘shape.background’, null);
        redAlarm.s(‘shape.background’, null);
    }
    else {
        yellowAlarm.s(‘shape.background’, ‘yellow’);
        redAlarm.s(‘shape.background’, ‘red’);
    }
}, 5);

HT 还封装了 setStyle 函数用来设置样式,可简写为 s,具体样式请参考 HT for
Web 样式手册:

JavaScript

for (var i = 0; i < 8; i++) {//因为有一些相似的元素我们设置的 tag
名类似,只是在后面换成了1、2、3,所以我们通过 for 循环来获取 var color =
stairIndex === i ? ‘#F6A623’ : ‘#CFCFCF’;
dataModel.getDataByTag(‘stair_1_’ + i).s(‘shape.border.color’, color);
dataModel.getDataByTag(‘stair_2_’ + i).s(‘shape.border.color’, color);
}

1
2
3
4
5
for (var i = 0; i < 8; i++) {//因为有一些相似的元素我们设置的 tag 名类似,只是在后面换成了1、2、3,所以我们通过 for 循环来获取
    var color = stairIndex === i ? ‘#F6A623’ : ‘#CFCFCF’;
    dataModel.getDataByTag(‘stair_1_’ + i).s(‘shape.border.color’, color);
    dataModel.getDataByTag(‘stair_2_’ + i).s(‘shape.border.color’, color);
}

我们还对“警告灯”的闪烁进行了定时控制,如果是偶数秒的时候,就将灯的背景颜色设置为“无色”,否则,如果是
yellowAlarm 则设置为“黄色”,如果是 redAlarm 则设置为“红色”:

if (new Date().getSeconds() % 2 === 1) {
yellowAlarm.s(‘shape.background’, null); redAlarm.s(‘shape.background’,
null); } else { yellowAlarm.s(‘shape.background’, ‘yellow’);
redAlarm.s(‘shape.background’, ‘red’); }

1
2
3
4
5
6
7
8
if (new Date().getSeconds() % 2 === 1) {
    yellowAlarm.s(‘shape.background’, null);
    redAlarm.s(‘shape.background’, null);
}
else {
    yellowAlarm.s(‘shape.background’, ‘yellow’);
    redAlarm.s(‘shape.background’, ‘red’);
}

整个例子就这么轻松地解决了,简直太轻松了。。。

有兴趣继续了解的小伙伴可以进入 HT for Web
官网查看各个手册进行学习。

2 赞 3 收藏
评论

图片 2

基于 HTML5 Canvas 的交互式地铁线路图

2018/03/14 · HTML5 ·
Canvas

原文出处: xhload3d   

 前言

前两天在 echarts
上寻找灵感的时候,看到了很多有关地图类似的例子,地图定位等等,但是好像就是没有地铁线路图,就自己花了一些时间捣鼓出来了这个交互式地铁线路图的
Demo,地铁线路上的点是在网上随便下载了一个,这篇文章记录自己的一些收获(毕竟我还是个菜鸟)以及代码的实现,希望能够帮到一些朋友。当然,如果有什么意见的可以直接跟我说,大家一起交流才会进步。

效果图

图片 6

地图稍微内容有点多,要全部展示,字显得有点小了,但是没关系,可以按照需求放大缩小,字体和绘制的内容并不会失真,毕竟都是用矢量绘制的~

发表评论

电子邮件地址不会被公开。 必填项已用*标注