我有以下代码来绘制形状(主要用于矩形),但HTML5绘图功能似乎绘制边框,其厚度以指定的线为中心。我希望在形状表面之外有一个边框,我不知所措。
Path.prototype.trace = function(elem, closePath) {
sd.context.beginPath();
sd.context.moveTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
sd.context.lineCap = "square";
for(var i=1; i<this.points.length; ++i) {
sd.context.lineTo(this.getStretchedX(i, elem.width), this.getStretchedY(i, elem.height));
}
if(closePath) {
sd.context.lineTo(this.getStretchedX(0, elem.width), this.getStretchedY(0, elem.height));
}
}
getStrechedX和getStretchedY在将形状应用于设置元素的宽度,高度和偏移位置后返回第n个顶点的坐标。
感谢Ken Fyrstenberg的回答我已经让它适用于矩形,但是这个解决方案可能不适用于其他形状。
在这里,我画了两个&#34;宽&#34;边框,一个减去每个位置的lineWidth的一半,另一个添加。它没有工作(正如预期的那样),因为它只会在一个案例中将粗线放在上面和左边,在另一个案例中放在右边 - 而不是&#34;外面&#34;形状。您还可以看到斜坡周围的白色区域。
我试着弄清楚如何让顶点手动绘制粗边框的路径(使用fill()
代替stroke()
)。
但事实证明我仍然遇到同样的问题:如何以编程方式确定边缘是在内部还是外部。这将需要一些三角法和重算法。出于我目前工作的目的,这太麻烦了。我想用它来绘制建筑物的地图。房间的墙壁需要在给定的尺寸之外绘制,但我现在仍然坚持独立的倾斜墙壁。
答案 0 :(得分:3)
您可以通过绘制两行来解决此问题:
收缩时,向x和y添加50%,从宽度和高度减去线宽(或2x50%)。
var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var lw50 = lineWidth * 0.5;
// outer line
ctx.lineWidth = lineWidth; // intended line width
ctx.strokeStyle = "#975"; // color for main line
ctx.strokeRect(40, 40, 100, 100); // full line
// inner line
ctx.lineWidth = 2; // inner line width
ctx.strokeStyle = "#000"; // color for inner line
ctx.strokeRect(40 + lw50, 40 + lw50, 100 - lineWidth, 100 - lineWidth);
<canvas></canvas>
对于更复杂的形状,您必须手动计算路径。这有点复杂,也许对于SO来说太宽泛了。你必须考虑切线,弯道角度,交叉点等等。
“作弊”的一种方法是:
下面的offset
值将决定内线的粗细,而directions
将决定分辨率。
var ctx = document.querySelector("canvas").getContext("2d");
var lineWidth = 20;
var offset = 0.5; // line "thickness"
var directions = 8; // increase to increase details
var angleStep = 2 * Math.PI / 8;
// shape
ctx.lineWidth = lineWidth; // intended line width
ctx.strokeStyle = "#000"; // color for inner line
ctx.moveTo(50, 100); // some random shape
ctx.lineTo(100, 20);
ctx.lineTo(200, 100);
ctx.lineTo(300, 100);
ctx.lineTo(200, 200);
ctx.lineTo(50, 100);
ctx.closePath();
ctx.stroke();
ctx.save()
ctx.clip(); // set as clipping mask
ctx.globalCompositeOperation = "destination-atop"; // draws "behind" existing drawings
for(var a = 0; a < Math.PI * 2; a += angleStep) {
ctx.setTransform(1,0,0,1, offset * Math.cos(a), offset * Math.sin(a));
ctx.drawImage(ctx.canvas, 0, 0);
}
ctx.restore(); // removes clipping, comp. mode, transforms
// set new color and redraw same path as previous
ctx.strokeStyle = "#975"; // color for inner line
ctx.stroke();
<canvas height=250></canvas>
答案 1 :(得分:2)
我已经迟到了,但是这是另一种方式来解决这个问题。&#34;一条复杂的道路。
它使用PathObject
来简化创建外部笔划的过程。
PathObject
保存用于定义复杂路径的所有命令和参数。
此PathObject还可以重放命令 - 从而可以重新定义/重绘已保存的路径。
PathObject类可重用。您可以使用它来保存需要重绘的任何路径(简单或复杂)。
Html5 Canvas很快将在上下文中内置自己的Path2D对象,但下面的示例中有一个跨浏览器的polyfill,可以在实现Path2D对象之前使用。
使用外部笔划应用银色衬里的云的插图。
&#34;在这里&#39;它是如何完成的......&#34;
创建一个PathObject
,可以保存用于定义复杂路径的所有命令和参数。此PathObject
也可以重放命令 - 从而可以重新定义保存的路径。 Html5 Canvas很快就会在上下文中内置自己的Path2D
对象,但下面的示例是一个跨浏览器的polyfill,可以在Path2D
对象实现之前使用。
使用PathObject保存复杂路径。
在主画布上播放路径命令并根据需要填充/描边。
在临时的内存中画布上播放路径命令。
在临时画布上:
设置所需外部笔画宽度两倍的context.lineWidth
并进行笔画。
设置globalCompositeOperation='destination-out'
并填写。这将导致复杂路径的内部被清除并变得透明。
将临时画布绘制到主画布上。这会导致主画布上现有的复杂路径获得&#34;外部笔画&#34;来自内存中的画布。
以下是示例代码和演示:
function log(){console.log.apply(console,arguments);}
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var canvas1=document.getElementById("canvas1");
var ctx1=canvas1.getContext("2d");
// A "class" that remembers (and can replay) all the
// commands & arguments used to define a context path
var PathObject=( function(){
// Path-related context methods that don't return a value
var methods = ['arc','beginPath','bezierCurveTo','clip','closePath',
'lineTo','moveTo','quadraticCurveTo','rect','restore','rotate',
'save','scale','setTransform','transform','translate','arcTo'];
var commands=[];
var args=[];
function PathObject(){
// add methods plus logging
for (var i=0;i<methods.length;i++){
var m = methods[i];
this[m] = (function(m){
return function () {
if(m=='beginPath'){
commands.length=0;
args.length=0;
}
commands.push(m);
args.push(arguments);
return(this);
};}(m));
}
};
// define/redefine the path by issuing all the saved
// path commands to the specified context
PathObject.prototype.definePath=function(context){
for(var i=0;i<commands.length;i++){
context[commands[i]].apply(context, args[i]);
}
}
//
PathObject.prototype.show=function(){
for(var i=0;i<commands.length;i++){
log(commands[i],args[i]);
}
}
//
return(PathObject);
})();
var x=75;
var y=100;
var scale=0.50;
// define a cloud path
var path=new PathObject()
.beginPath()
.save()
.translate(x,y)
.scale(scale,scale)
.moveTo(0, 0)
.bezierCurveTo(-40, 20, -40, 70, 60, 70)
.bezierCurveTo(80, 100, 150, 100, 170, 70)
.bezierCurveTo(250, 70, 250, 40, 220, 20)
.bezierCurveTo(260, -40, 200, -50, 170, -30)
.bezierCurveTo(150, -75, 80, -60, 80, -30)
.bezierCurveTo(30, -75, -20, -60, 0, 0)
.restore();
// fill the blue sky on the main canvas
ctx.fillStyle='skyblue';
ctx.fillRect(0,0,canvas.width,canvas.height);
// draw the cloud on the main canvas
path.definePath(ctx);
ctx.fillStyle='white';
ctx.fill();
ctx.strokeStyle='black';
ctx.lineWidth=2;
ctx.stroke();
// draw the cloud's silver lining on the temp canvas
path.definePath(ctx1);
ctx1.lineWidth=20;
ctx1.strokeStyle='silver';
ctx1.stroke();
ctx1.globalCompositeOperation='destination-out';
ctx1.fill();
// draw the silver lining onto the main canvas
ctx.drawImage(canvas1,0,0);
&#13;
body{ background-color: ivory; }
canvas{border:1px solid red;}
&#13;
<h4>Main canvas with original white cloud + small black stroke<br>The "outside silver lining" is from the temp canvas</h4>
<canvas id="canvas" width=300 height=300></canvas>
<h4>Temporary canvas used to create the "outside stroke"</h4>
<canvas id="canvas1" width=300 height=300></canvas>
&#13;