在现在前端圈大行其道的 React 和 Vue 中,可复用的组件可能是他们大受欢迎的原因之一,
在 HT 的产品中也有组件的概念,不过在 HT 中组件的开发是依托于 HTML5 Canvas 的技术去实现的,
也就是说如果你有过使用 Canvas 的开发经验你就可以来封装自己的组件。
下面我以一个进度环为例,来探究一下如何使用ht.js
封装出一个拓扑组件。
自定义组件 除了
HT
预定义的组件类型外,用户还可以自定义扩展类型,自定义有两种方式:
type
值设置成绘制函数:function(g, rect, comp, data, view){}
ht.Default.setCompType(name, funtion(g, rect, comp, data, view){})
注册组件类型,矢量type
值设置成相应的注册名在这里我选用第一种通过形如
ht.Default.setImage('circle-progress-bar', { width: 100, height: 100, comps: [ { type: function(g, rect, comp, data, view) { // ... } } ] });
这样的方式完成组件的声明,那么 function(g, rect, comp, data, view) { }
中的内容就是我们接下来需要关注的了
progressPercentage {百分比}
linearOuter {颜色数组}
linearInner {颜色数组}
fontScale {数字}
showOrigin {布尔}
progressLineCap {线帽样式}
准备工作结束后下面就是 Canvas 的时间了
最后通过简单的配置就可以在网页上呈现出这个进度环了 var dataModel = new ht.DataModel(); var graphView = new ht.graph.GraphView(dataModel); var circle1 = new ht.Node(); circle1.setPosition(150, 150); circle1.setSize(200, 200); circle1.setImage('circle-progress-bar'); circle1.a({ progressPercentage: 0.48, linearOuter: ['#26a67b', '#0474d6'], linearInner: ['#004e92', '#000000'], fontScale: 1, showOrigin: true, progressLineCap: 'butt', backgroundColor: 'rgb(61,61,61)' }); dataModel.add(circle1); // 这次多生成几个 不过代码相似 在此就不赘述了
完整代码如下
View Code
在这个部分有几点可供参考
fontScale
字体缩放比例progressPercentage
进度百分比由于进度环是一个圆形的组件,那么在这里有两点供参考
rect.width
和 rect.height
不相等的时候我们需要自己来设定一个 width,
让圆在这个以 width 为边的正方形中绘制,而 width 的值就是 rect.width
和 rect.height
中较短的一边,
而这么做的理由是这样绘制圆自适应性能力会更好,并且圆心也直会在 (rect.width/2, rect.height/2)
这一点上。
var rectWidth = rect.width; var rectHeight = rect.height; var width = rectWidth < rectHeight ? rectWidth : rectHeight;
g.createLinearGradient(0, 0, rect.width, rect.height)
设置渐变色就会出现下面的效果,右下方的蓝色不见了。
不过如果按照如下代码的方式设置渐变色就会出现下面的效果就会出现预期的效果了。 var posX = rectWidth / 2; var posY = rectHeight / 2; var linear = rectWidth < rectHeight ? g.createLinearGradient(0, posY - width / 2, width, posY + width / 2) : g.createLinearGradient(posX - width / 2, 0, posX + width / 2, width);
原因其实很简单,就是渐变颜色方向的起点和终点并没有随着 width 的改变而改变。
如图所示以rectWidth > rectHeight
为例
在绘制组件的过程中,我们需要把一些边界条件和特殊情况考虑到,来保持组件的扩展性和稳定性
下面就是一些我的心得
save
和 restore
,以此来保障 g 操作的不影响后续的扩展开发。
g.save() // g 操作 // ... // ... g.restore()
save/restore
设想一下,我们正在用 10 像素宽,颜色为红色的笔画图,然后把画笔设置成1像素宽,颜色变成绿色。绿色画完之后呢,我们想接着用10像素的红色来画,如果没有 save 与 restore,那我们就不得不重新设置一遍画笔——如果画笔状态过多,那我们的代码就会大量增加;而且,这些设置过程是重复而乏味的。
最后保存的最先还原!restore 总是还原离他最近的 save 点(已经还原的不能第2次还原到他)。
另外 save 和 restore 一般是改变了 transform 或 clip 才需要,大部分情况下不需要,例如你设置了颜色、宽度等等参数,下次要绘制这些的人会自己再设置这些,所以能尽量不用 save/restore 的地方可以尽量不用,那也是有代价的
造成这个结果的原因是 scale 操作的参考点位置不对 下面我们使用矩形的例子详细解释一下 // 原矩形 ctx.save(); ctx.beginPath(); ctx.strokeRect(0, 0, 400, 400); ctx.restore(); // 缩放后的矩形 ctx.save(); ctx.beginPath(); ctx.scale(0.75, 0.75); ctx.strokeRect(0, 0, 400, 400); ctx.restore();
这时 scale 的参考点是(0,0)
所以,中心缩放没有按照我们预期的进行
当修改参考点的坐标为(50,50)
之后,中心缩放就正常了
那么这个(50,50)
是怎么得来的?
根据上图我们不难看出这个距离其实就是 (缩放前的边长 - 缩放后的边长) / 2
得到得
公式就是 width * (1 - scale) / 2
在这个例子中套用一下就是 400 * (1 - 0.75) / 2 = 50
// 原矩形 ctx.save(); ctx.beginPath(); ctx.strokeRect(0, 0, 400, 400); ctx.restore(); // 缩放后的矩形 ctx.save(); ctx.beginPath(); ctx.translate(50, 50) ctx.scale(0.75, 0.75); ctx.strokeRect(0, 0, 400, 400); ctx.restore();
我们把上面得公式在做进一步的扩展,让它的适用性更强
width * (1 - scale) / 2 -> width / 2 * (1 - scale) -> posX * (1 - scale) height * (1 - scale) / 2 -> height / 2 * (1 - scale) -> posY * (1 - scale)
在这里也需要明确一点 posX = x + (width / 2)
posY = y + (height / 2)
在进一步抽象成函数
function centerScale(ctx, posX, posY, scaleX, scaleY) { ctx.translate(posX * (1 - scaleX), posY * (1 - scaleY)); ctx.scale(scaleX, scaleY); }
那么其中的文字缩放也是如出一辙
var fontScale = 0.75 g.fillStyle = 'white'; g.textAlign = 'center'; g.font = fontSize + 'px Arial'; g.translate(posX * (1 - fontScale), posY * (1 - fontScale)); g.scale(fontScale, fontScale); g.fillText(progressPercentage + '%', posX, posY + fontSize / 3);
当然结果也是很不错的?,文字的缩放功能实现了