<canvas> clearRect() in a letter shape - javascript

I've recently started learning web techniques, so I'm still a newbie regarding pretty much everything. I've stumbled upon this portfolio site, link, which I'm trying to "recreate" as part of my learning process/practice.
Here I'm interested in the background of the page, and how to make that transparent letter on canvas. In my current work, I have a html background image set, fillRect() on a full screen with opacity of 0.9 , and now I don't know how to make use of clearRect().
The question is: Am I on the right track, and is there any way that I'm not aware of, in which I can use clearRect() to clear pixels on canvas in a letter shape? Like, without manually defining a path for clearRect(), but where I would only input a letter and clear pixels in its shape. Sorry if I posted my current code below the wrong way, it's my first time posting here.
var canvas = document.getElementById('layout');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
}
$(document).ready(function() {
window.addEventListener('resize', setCanvasSize); //false
window.addEventListener('resize', draw); //false
//set canvas size----------------------------------------
setCanvasSize();
function setCanvasSize() {
canvas.width = $(window).width();
canvas.height = $(window).height();
}
//draw---------------------------------------------------
draw();
function draw() {
var x = canvas.width;
var y = canvas.height;
ctx.fillStyle = "white";
ctx.fillRect(0, 0, x, y);
ctx.clearRect(50, 30, 110, 35);
}
});

You can achieve effects such as this using globalCompositeOperation.
Here's an example where some text is used as shape to erase drawn pixels:
var canvas = document.createElement("canvas");
canvas.width = 300;
canvas.height = 200;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
ctx.fillStyle = "rgba(255, 255, 255, 0.8";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "destination-out";
ctx.font = "70pt Arial";
ctx.fillText("Text", 50, 130);
Here's the JSFiddle: http://jsfiddle.net/eSpUv/

Related

Is there a way to make a rectangular area 0 alpha (transparent) after it has been drawn on to? [duplicate]

After experimenting with composite operations and drawing images on the canvas I'm now trying to remove images and compositing. How do I do this?
I need to clear the canvas for redrawing other images; this can go on for a while so I don't think drawing a new rectangle every time will be the most efficient option.
Given that canvas is a canvas element or an OffscreenCanvas object, use clearRect:
const context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
Use: context.clearRect(0, 0, canvas.width, canvas.height);
This is the fastest and most descriptive way to clear the entire canvas.
Do not use: canvas.width = canvas.width;
Resetting canvas.width resets all canvas state (e.g. transformations, lineWidth, strokeStyle, etc.), it is very slow (compared to clearRect), it doesn't work in all browsers, and it doesn't describe what you are actually trying to do.
Dealing with transformed coordinates
If you have modified the transformation matrix (e.g. using scale, rotate, or translate) then context.clearRect(0,0,canvas.width,canvas.height) will likely not clear the entire visible portion of the canvas.
The solution? Reset the transformation matrix prior to clearing the canvas:
// Store the current transformation matrix
context.save();
// Use the identity matrix while clearing the canvas
context.setTransform(1, 0, 0, 1, 0, 0);
context.clearRect(0, 0, canvas.width, canvas.height);
// Restore the transform
context.restore();
Edit:
I've just done some profiling and (in Chrome) it is about 10% faster to clear a 300x150 (default size) canvas without resetting the transform. As the size of your canvas increases this difference drops.
That is already relatively insignificant, but in most cases you will be drawing considerably more than you are clearing and I believe this performance difference be irrelevant.
100000 iterations averaged 10 times:
1885ms to clear
2112ms to reset and clear
If you are drawing lines, make sure you don't forget:
context.beginPath();
Otherwise the lines won't get cleared.
Others have already done an excellent job answering the question but if a simple clear() method on the context object would be useful to you (it was to me), this is the implementation I use based on answers here:
CanvasRenderingContext2D.prototype.clear =
CanvasRenderingContext2D.prototype.clear || function (preserveTransform) {
if (preserveTransform) {
this.save();
this.setTransform(1, 0, 0, 1, 0, 0);
}
this.clearRect(0, 0, this.canvas.width, this.canvas.height);
if (preserveTransform) {
this.restore();
}
};
Usage:
window.onload = function () {
var canvas = document.getElementById('canvasId');
var context = canvas.getContext('2d');
// do some drawing
context.clear();
// do some more drawing
context.setTransform(-1, 0, 0, 1, 200, 200);
// do some drawing with the new transform
context.clear(true);
// draw more, still using the preserved transform
};
This is 2018 and still there is no native method to completely clear canvas for redrawing. clearRect() does not clear the canvas completely. Non-fill type drawings are not cleared out (eg. rect())
1.To completely clear canvas irrespective of how you draw:
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
Pros: Preserves strokeStyle, fillStyle etc.; No lag;
Cons: Unnecessary if you are already using beginPath before drawing anything
2.Using the width/height hack:
context.canvas.width = context.canvas.width;
OR
context.canvas.height = context.canvas.height;
Pros: Works with IE
Cons: Resets strokeStyle, fillStyle to black; Laggy;
I was wondering why a native solution does not exist. Actually, clearRect() is considered as the single line solution because most users do beginPath() before drawing any new path. Though beginPath is only to be used while drawing lines and not closed path like rect().
This is the reason why the accepted answer did not solve my problem and I ended up wasting hours trying different hacks. Curse you mozilla
Chrome responds well to: context.clearRect ( x , y , w , h ); as suggested by #Pentium10 but IE9 seems to completely ignore this instruction.
IE9 seems to respond to: canvas.width = canvas.width; but it doesn't clear lines, just shapes, pictures and other objects unless you also use #John Allsopp's solution of first changing the width.
So if you have a canvas and context created like this:
var canvas = document.getElementById('my-canvas');
var context = canvas.getContext('2d');
You can use a method like this:
function clearCanvas(context, canvas) {
context.clearRect(0, 0, canvas.width, canvas.height);
var w = canvas.width;
canvas.width = 1;
canvas.width = w;
}
Use clearRect method by passing x,y co-ordinates and height and width of canvas. ClearRect will clear whole canvas as :
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
A quick way is to do
canvas.width = canvas.width
Idk how it works but it does!
there are a ton of good answers here.
one further note is that sometimes it's fun to only partially clear the canvas.
that is, "fade out" the previous image instead of erasing it entirely.
this can give nice trails effects.
it's easy. supposing your background color is white:
// assuming background color = white and "eraseAlpha" is a value from 0 to 1.
myContext.fillStyle = "rgba(255, 255, 255, " + eraseAlpha + ")";
myContext.fillRect(0, 0, w, h);
This is what I use, regardless boundaries and matrix transformations:
function clearCanvas(canvas) {
const ctx = canvas.getContext('2d');
ctx.save();
ctx.globalCompositeOperation = 'copy';
ctx.strokeStyle = 'transparent';
ctx.beginPath();
ctx.lineTo(0, 0);
ctx.stroke();
ctx.restore();
}
Basically, it saves the current state of the context, and draws a transparent pixel with copy as globalCompositeOperation. Then, restores the previous context state.
I always use
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
For a custom color, and
ctx.clearRect(0, 0, canvas.width, canvas.height);
For making the canvas transparent when clearing
This worked for my pieChart in chart.js
<div class="pie_nut" id="pieChartContainer">
<canvas id="pieChart" height="5" width="6"></canvas>
</div>
$('#pieChartContainer').html(''); //remove canvas from container
$('#pieChartContainer').html('<canvas id="pieChart" height="5" width="6"></canvas>'); //add it back to the container
the shortest way:
canvas.width += 0
I have found that in all browsers I test, the fastest way is to actually fillRect with white, or whataever color you would like. I have a very large monitor and in full screen mode the clearRect is agonizingly slow, but the fillRect is reasonable.
context.fillStyle = "#ffffff";
context.fillRect(0,0,canvas.width, canvas.height);
The drawback is that the canvas is no longer transparent.
private clearCanvas() {
const canvas: HTMLCanvasElement = this.ctx.canvas
this.ctx.save()
this.ctx.setTransform(1, 0, 0, 1, 0, 0)
this.ctx.clearRect(0, 0, canvas.width, canvas.height)
this.ctx.restore()
}
in webkit you need to set the width to a different value, then you can set it back to the initial value
function clear(context, color)
{
var tmp = context.fillStyle;
context.fillStyle = color;
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.fillStyle = tmp;
}
A simple, but not very readable way is to write this:
var canvas = document.getElementId('canvas');
// after doing some rendering
canvas.width = canvas.width; // clear the whole canvas
Context.clearRect(starting width, starting height, ending width, ending height);
Example: context.clearRect(0, 0, canvas.width, canvas.height);
I always use this
ctx.clearRect(0, 0, canvas.width, canvas.height)
window.requestAnimationFrame(functionToBeCalled)
NOTE
combining clearRect and requestAnimationFrame allows for more fluid animation if that is what you're going for
If you use clearRect only, if you have it in a form to submit your drawing, you'll get a submit instead the clearing, or maybe it can be cleared first and then upload a void drawing, so you'll need to add a preventDefault at the beggining of the function:
function clearCanvas(canvas,ctx) {
event.preventDefault();
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
<input type="button" value="Clear Sketchpad" id="clearbutton" onclick="clearCanvas(canvas,ctx);">
Hope it helps someone.
This is a Free hand drawing Canvas with a Clear Canvas Button.
See this live example of a canvas which you can draw on and also when required clear it for redrawing clearRect() is used to delete the prersent canvas and fillRect() is used to again draw the initial canvas which was clean and had no drawings on it.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
painting = false,
lastX = 0,
lastY = 0,
lineThickness = 1;
canvas.width=canvas.height = 250;
ctx.fillRect(0, 0, 250, 250);
canvas.onmousedown = function(e) {
painting = true;
ctx.fillStyle = "#ffffff";
lastX = e.pageX - this.offsetLeft;
lastY = e.pageY - this.offsetTop;
};
canvas.onmouseup = function(e){
painting = false;
}
canvas.onmousemove = function(e) {
if (painting) {
mouseX = e.pageX - this.offsetLeft;
mouseY = e.pageY - this.offsetTop;
// find all points between
var x1 = mouseX,
x2 = lastX,
y1 = mouseY,
y2 = lastY;
var steep = (Math.abs(y2 - y1) > Math.abs(x2 - x1));
if (steep){
var x = x1;
x1 = y1;
y1 = x;
var y = y2;
y2 = x2;
x2 = y;
}
if (x1 > x2) {
var x = x1;
x1 = x2;
x2 = x;
var y = y1;
y1 = y2;
y2 = y;
}
var dx = x2 - x1,
dy = Math.abs(y2 - y1),
error = 0,
de = dy / dx,
yStep = -1,
y = y1;
if (y1 < y2) {
yStep = 1;
}
lineThickness = 4;
for (var x = x1; x < x2; x++) {
if (steep) {
ctx.fillRect(y, x, lineThickness , lineThickness );
} else {
ctx.fillRect(x, y, lineThickness , lineThickness );
}
error += de;
if (error >= 0.5) {
y += yStep;
error -= 1.0;
}
}
lastX = mouseX;
lastY = mouseY;
}
}
var button=document.getElementById("clear");
button.onclick=function clearcanvas(){
canvas=document.getElementById("canvas"),
ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, 250, 250);
canvas.width=canvas.height = 250;
ctx.fillRect(0, 0, 250, 250);}
#clear{border-radius:10px;
font-size:8px !important;
position:absolute;
top:1px;}
#canvas{border-radius:10px}
<link href="https://www.w3schools.com/w3css/4/w3.css" rel="stylesheet"/>
<button id="clear" class="w3-padding w3-xxlarge w3-pink" type="button">Clear Canvas</button>
<canvas id="canvas"></canvas>
There is now a .reset() method which will not only clear the canvas buffer but also completely reset all the properties of the context (styles etc.), reset its transformation matrix, clear its current sub-path, clear its states stack (the one controlled by save() and restore()), and remove all the clipping regions.
context.reset();
// now 'context' is clear as new
Basically, it has the same effects as canvas.width += 0, except that it's more idiomatic.
However it seems that in current Chromium's implementation it's as slow as canvas.width += 0, it also does generate a new buffer instead of simply clearing the previous one (resulting in more memory garbage). Another caveat is that it's currently only supported in Chromium browsers.
Though to polyfill it you can go the Chrome way
if (!CanvasRenderingContext2D.prototype.reset) {
CanvasRenderingContext2D.prototype.reset = function() {
this.canvas.width += 0;
};
}
if (!OffscreenCanvasRenderingContext2D.prototype.reset) {
OffscreenCanvasRenderingContext2D.prototype.reset = function() {
this.canvas.width += 0;
};
}
These are all great examples of how you clear a standard canvas, but if you are using paperjs, then this will work:
Define a global variable in JavaScript:
var clearCanvas = false;
From your PaperScript define:
function onFrame(event){
if(clearCanvas && project.activeLayer.hasChildren()){
project.activeLayer.removeChildren();
clearCanvas = false;
}
}
Now wherever you set clearCanvas to true, it will clear all the items from the screen.
fastest way:
canvas = document.getElementById("canvas");
c = canvas.getContext("2d");
//... some drawing here
i = c.createImageData(canvas.width, canvas.height);
c.putImageData(i, 0, 0); // clear context by putting empty image data

Canvas shape inner shadow + blending mode

so this has been driving me crazy for the past few days.
I'm trying to replicate Photoshop's inner shadow in JS with different blending modes like overlay.
There are easy ways to add shadows to shapes like ctx.shadowBlur or ctx.filter = 'drop-shadow(...)', but these only generate outer shadows. You can create inner shadow with some composition magic with xor, but this leaves the image with not smooth edges (I guess xor doesn't handle anti-alias really well), like in this example:
https://jsfiddle.net/89pes8ap/1/
So, I had another idea that kind of worked, because it used xor only once:
https://jsfiddle.net/3cnwtvyj/
But as you can see the overlay-ed version still doesn't have smooth edges.
So, my question is this: how can you add smooth inner shadow with different blending modes that could work with all kinds of shapes?
Composite Operations do work on the alpha channel, and hence don't go well with shadows.
Unfortunately, I think there is not much you can do to circumvent this for your shadow over transparent background, moreover for a circle.
One little thing I can see though is that you actually don't need the xor part, and can replace it with a path composed of a rect and an arc that you'd fill as 'evenodd'. This will create the hole directly, reducing a little bit antialiasing artifacts.
drawOP();
drawEvenOdd();
function drawEvenOdd() {
let canvas = document.getElementById('evenodd');
canvas.width = 150;
canvas.height = 200;
let ctx = canvas.getContext('2d')
ctx.textAlign = 'center';
ctx.shadowColor = 'black';
ctx.shadowBlur = 10 * 2;
ctx.shadowOffsetY = 5;
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
// will draw an hole
ctx.fill('evenodd');
// remove shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = ctx.shadowOffsetY = 0;
ctx.globalCompositeOperation = 'destination-out';
ctx.fill('evenodd');
ctx.globalCompositeOperation = 'source-over';
ctx.fillText('evenodd', 100, 180)
}
function drawOP() {
let canvas = document.getElementById('OP')
canvas.width = 150;
canvas.height = 200;
let ctx = canvas.getContext('2d')
ctx.textAlign = 'center';
ctx.fillStyle = 'black'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.globalCompositeOperation = 'xor'
ctx.arc(100, 100, 50, 0, 2 * Math.PI)
ctx.fill()
ctx.filter = 'drop-shadow(0 5px 10px black)'
ctx.drawImage(canvas, 0, 0)
ctx.filter = 'none';
ctx.fillText('OP', 100, 180)
}
<canvas id="OP"></canvas>
<canvas id="evenodd"></canvas>

Why is nothing appearing on my canvas?

I am such a noob and I have no idea why nothing is appearing on my canvas. Can someone help me out? I wanted to have a black box on my canvas, that was the intention anyway.
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
canvas.width = 1040;
canvas.height = 400;
canvas.style.display='block';
canvas.style.marginLeft="auto";
canvas.style.marginRight="auto";
var mainFunction= function(){
ctx.fillStyle = "rgb(0, 0, 0)";
ctx.beginPath();
ctx.rect(0, 0, canvas.width, canvas.height);
ctx.closePath();
ctx.fill();
requestAnimationFrame(mainFunction);
};
mainFunction();
You have created a <canvas> element but have not attached it anywhere in the DOM. Perhaps you want something like
document.body.appendChild(canvas);
or another suitable container.
Basically, you do not have an attached canvas to the DOM.
Create a html file with <canvas id="root"></canvas>;
var canvas = document.getElementById("root");

Pattern gets shifted when painting at the bottom edge of the canvas

I would like to paint a repeating grass pattern at the bottom of a canvas. The image does repeat, but it's vertically shifted.
The pattern is based on a 192x50 image:
I've noticed that if I paint from the y-coordinates 50, 100, 150, and so on, the pattern is displayed correctly. It doesn't work at other coordinates.
The resulting canvas has a vertically shifted grass pattern:
I don't know why it gets shifted like that.
Below is my code.
HTML:
<canvas id="myCanvas" style="display: block;">
</canvas>
JavaScript:
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
// Grass Background Image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
bgReady = true;
};
bgImage.src = "img/grass.png";
I do the following in a loop:
if (bgReady) {
ctx.drawImage(bgImage,0, canvas.height -50,192,50);
var ptrn = ctx.createPattern(bgImage, "repeat"); // Create a pattern with this image, and set it to repeat".
ctx.fillStyle = ptrn;
ctx.fillRect(0,canvas.height - bgImage.height,canvas.width, 50); // context.fillRect(x, y, width, height);
}
I tried to put your code in jsFiddle and it seems to work as expected:
var canvas = document.getElementById('myCanvas');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
// Grass Background Image
var bgImage = new Image();
bgImage.onload = function () {
var ctx = canvas.getContext("2d");
ctx.drawImage(bgImage,0, canvas.height -50,192,50);
var ptrn = ctx.createPattern(bgImage, "repeat"); // Create a pattern with this image, and set it to repeat".
ctx.fillStyle = ptrn;
ctx.fillRect(0,canvas.height - bgImage.height,canvas.width, 50); // context.fillRect(x, y, width, height);
};
bgImage.src = "data:image/png;base64,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";
https://jsfiddle.net/or68kcpc/
The error must be somewhere else.
The problem is that the pattern is calculated starting from the top left corner of the canvas. If you paint starting at a canvas y-coordinate that isn't an integer multiple of the image height, the visible portion of the pattern won't start at the top of the image.
To fix this, shift the drawing context down before painting with the pattern, then paint the pattern at a location shifted up, then shift the context back up:
var shiftY = canvas.height % image.height;
context.translate(0, shiftY);
context.fillRect(0, canvas.height - image.height - shiftY,
canvas.width, image.height);
context.translate(0, -shiftY);
Run the snippet below to see a demonstration. The canvas height is 120, which means that we start painting from the y-coordinate 120 - 50 = 70, which is not a multiple of 50.
To correct for this, we shift the context down by 120 % 50 = 20 and then shift the painting location up by 20. Thus, the pattern gets painted on the shifted context at the y-coordinate (70 - 20) = 50, which is a multiple of the image height.
var canvas = document.getElementById('myCanvas'),
context = canvas.getContext('2d');
canvas.height = 120;
canvas.width = 500;
var image = new Image();
image.src = 'http://i.stack.imgur.com/2bfPb.png';
image.onload = function () {
var pattern = context.createPattern(image, "repeat");
context.fillStyle = pattern;
var shiftY = canvas.height % image.height;
context.translate(0, shiftY);
context.fillRect(0, canvas.height - image.height - shiftY,
canvas.width, image.height);
context.translate(0, -shiftY);
};
#myCanvas {
border: 1px solid #666;
}
<canvas id="myCanvas"></canvas>
Let me make one more observation. The loop in your code is inefficient and the bgReady flag is unnecessary because you can run the painting code in the image.onload function, as I have done in my snippet.

Canvas polygons not touching properly

So I've been fiddling with the canvas element, and I seem to have run into a situation that is highly irritating, yet I haven't been able to find a solution.
Say that two polygons are drawn on a canvas, and that they should be touching each other. Where one polygon is drawn like this:
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
A simple version is implemented in this fiddle.
As you can probably see there is a thin line between these shapes. How can I avoid it? I have tried the solutions here, but they don't really seem to refer to this case, because I'm dealing with diagonal lines.
One solution
You could always use the stroke-line trick, but depending on your goal:
If it is to show many polygons next to each other, you could look at the polygons as simple squares.
Draw them in as such in an off-screen canvas next to each other. This will produce a result with no gaps.
Then transform the main canvas into the position you want those polygons to appear. Add rotation and/or skew depending on goal.
Finally, draw the off-screen canvas onto the main canvas as an image. Problem gone.
This will give you an accurate result with no extra steps in stroking, and the calculations for the boxes becomes very simple and fast to do (think 2d grid).
You have to use an off-screen canvas though. If you transform main canvas and draw in the shapes you will encounter the same problem as already present. This is because each point is transformed and if there is need for interpolation it will be calculated for each path shape separately. Drawing in an image will add interpolation on the whole surface, and only where there are gaps (non-opaque alpha). As we already are "gap-free" this is no longer a problem.
This will require an extra step in planning to place them correctly, but this is a simple step.
Example
Step 1 - draw boxes into an off-screen canvas:
This code draws on the off-screen canvas resulting in two boxes with no gap:
(the example uses an on-screen to show result, see next step for usage of off-screen canvas)
var ctx = document.querySelector("canvas").getContext("2d");
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 50, 50);
ctx.fillRect(60, 10, 50, 50);
<canvas/>
Step 2 - transform main canvas and draw in off-screen canvas
When drawn into main canvas with transformation set, the result will be (pseudo-random transformation just to show):
var ctx = document.querySelector("canvas").getContext("2d");
// off-screen canvas
var octx = document.createElement("canvas").getContext("2d");
octx.fillStyle = "red";
octx.fillRect(10, 10, 50, 50);
octx.fillRect(60, 10, 50, 50);
// transform and draw to main
ctx.translate(80, 0);
ctx.rotate(0.5, Math.PI);
ctx.transform(1, 0, Math.tan(-0.5),1, 0,0); // skew
ctx.drawImage(octx.canvas, 0, 0);
<canvas />
Step 3 (optional) - Interaction
If you want to interact with the boxes you simply apply the same transform, then add path for a box and hit-test it against the mouse position. Redraw a single state, erase by clearing and draw back the off-screen canvas on top:
var ctx = document.querySelector("canvas").getContext("2d");
// off-screen canvas
var octx = document.createElement("canvas").getContext("2d");
octx.fillStyle = "red";
octx.fillRect(10, 10, 50, 50);
octx.fillRect(60, 10, 50, 50);
// allow us to reuse some of the steps:
function getTransforms() {
ctx.setTransform(1,0,0,1,0,0);
ctx.translate(80, 0);
ctx.rotate(0.5, Math.PI);
ctx.transform(1, 0, Math.tan(-0.5),1, 0,0); // skew
}
function clear() {
ctx.setTransform(1,0,0,1,0,0);
ctx.clearRect(0,0,300,150);
}
function redraw() {
ctx.drawImage(octx.canvas, 0, 0);
}
getTransforms();
redraw();
ctx.canvas.onmousemove = function(e) {
var r = this.getBoundingClientRect(),
x = e.clientX - r.left, y = e.clientY - r.top;
// box 1 (for many, use array)
ctx.beginPath();
ctx.rect(10, 10, 50, 50);
clear(); // these can be optimized to use state-flags
getTransforms(); // so they aren't redraw for every move...
redraw();
// just one box check here
if (ctx.isPointInPath(x, y)) {
ctx.fill();
}
};
<canvas />
Yes, it's annoying when filled polygons result in that tiny gap. It's especially common on diagonals that should theoretically meet.
A common workaround is to put a half-pixel, same-colored stroke around the polygons:
//Some basic setup ...
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var oX = 50;
var oY = 50;
var h = 33;
var k = 50;
ctx.fillStyle = 'red';
ctx.strokeStyle='red';
ctx.lineWidth=0.50;
//Draw one polygon
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
//Draw another polygon
oX = oX+k;
oY = oY+h;
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
//Some basic setup ...
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var oX = 50;
var oY = 50;
var h = 33;
var k = 50;
ctx.fillStyle = 'red';
ctx.strokeStyle='red';
ctx.lineWidth=0.50;
//Draw one polygon
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
//Draw another polygon
oX = oX+k;
oY = oY+h;
ctx.beginPath();
ctx.moveTo(oX,oY);
ctx.lineTo(oX=oX+k,oY=oY-h);
ctx.lineTo(oX=oX+k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY+h);
ctx.lineTo(oX=oX-k,oY=oY-h);
ctx.fill();
ctx.stroke();
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>

Categories

Resources