HTML Canvas Not Working (Can't draw stuff) - javascript

I'm trying to create a drawing app using HTML and JavaScript
So far, I've been able to print a bunch of stuff on console regarding every method that I was intending to use in the drawing process and it looks like every method is being called as expected. However, nothing is being drawn on the canvas
Here is the code;
function CanvasItem({index}){
let drawing = false
window.addEventListener('load', handleCanvas)
function startedDrawing(e, ctx){
drawing = true
console.log("started drawing")
draw(e, ctx)
}
function finishedDrawing(e, ctx){
drawing = false
console.log("finishd drawing")
ctx.beginPath()
}
function draw(e, ctx){
if(!drawing) return
console.log('context: ', ctx)
console.log("x and y", e.clientX, e.clientY)
ctx.lineWidth ='1'
ctx.lineCap = 'round'
ctx.strokeStyle = 'red'
ctx.fillStyle = 'red'
ctx.lineTo(e.clientX, e.clientY)
ctx.stroke()
ctx.beginPath()
ctx.moveTo(e.clientX, e.clientY)
}
function handleCanvas(e){
const canv = document.getElementById(`canvas-item-${index}`)
let ctx = canv?.getContext('2d')
if(ctx){
ctx.width = window.innerHeight * 9/100
ctx.height = window.innerHeight * 9/100
window.addEventListener('resize', ()=>{
ctx.width = window.innerHeight * 9/100
ctx.height = window.innerHeight * 9/100
})
}
canv.addEventListener('mousedown', (e)=>startedDrawing(e, ctx))
canv.addEventListener('mouseup', (e)=>finishedDrawing(e, ctx))
}
return (
<canvas id={`canvas-item-${index}`} className="canvas-item">
</canvas>
)
}

Related

Line drawing in canvas with gradient

I'm looking for a solution to freehand draw a line with a gradient in canvas like this:
I already found this, but the gradient seems to be 'locked' on the background and is not following the line.
window.addEventListener('load', ()=>{
resize(); // Resizes the canvas once the window loads
document.addEventListener('mousedown', startPainting);
document.addEventListener('mouseup', stopPainting);
document.addEventListener('mousemove', sketch);
window.addEventListener('resize', resize);
});
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
function resize(){
ctx.canvas.width = window.innerWidth;
ctx.canvas.height = window.innerHeight;
}
let coord = {x:0 , y:0};
let paint = false;
function getPosition(event){
coord.x = event.clientX - canvas.offsetLeft;
coord.y = event.clientY - canvas.offsetTop;
}
function startPainting(event){
paint = true;
getPosition(event);
}
function stopPainting(){
paint = false;
}
function sketch(event){
if (!paint) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
var gradient = ctx.createLinearGradient(0, 0, 200, 0);
gradient.addColorStop(0.00, 'grey');
gradient.addColorStop(1 / 2, 'white');
gradient.addColorStop(1.00, 'grey');
ctx.strokeStyle = gradient;
ctx.moveTo(coord.x, coord.y);
getPosition(event);
ctx.lineTo(coord.x , coord.y);
ctx.stroke();
}
Can somebody help me please?
I found a way to do it:
var context = canvas.getContext("2d");
context.strokeStyle = '#000000';
context.fillStyle = '#000000';
context.beginPath();
context.moveTo(10, 10);
context.lineTo(50, 10);
context.lineWidth = 2;
context.stroke();
context.beginPath();
context.lineWidth = 15;
context.moveTo(10, 30);
context.lineTo(50, 30);
context.stroke();
context.beginPath();
context.moveTo(10, 50);
context.lineTo(50, 50);
context.lineWidth = 2;
context.stroke();
Where I reconstructed the gradient with multiple lines and a blur on those lines.

I'm making a drawing based web-app and I'm using HTML Canvas to handle the drawing, however the drawing is offset to the right a lot?

const map = document.getElementById('map')
const canvas = document.createElement('canvas')
const ctx = canvas.getContext('2d')
const grid = document.getElementById('grid')
function resize() {
canvas.width = map.offsetWidth
canvas.height = map.offsetHeight
ctx.width = map.offsetWidth
ctx.height = map.offsetHeight
}
resize();
grid.appendChild(canvas)
canvas.style.gridColumn = 2
canvas.style.gridRow = 1
let pos = { x: 0, y: 0 };
window.addEventListener('resize', resize);
document.addEventListener('mousemove', draw);
document.addEventListener('mousedown', setPosition);
document.addEventListener('mouseenter', setPosition);
function setPosition(e) {
pos.x = e.clientX;
pos.y = e.clientY;
}
function draw(e) {
if (e.buttons !== 1) return;
ctx.beginPath();
ctx.lineWidth = 5;
ctx.lineCap = 'round';
ctx.strokeStyle = '#c0392b';
ctx.moveTo(pos.x, pos.y);
setPosition(e);
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
Heres the code that generates the canvas relative to the size of a picture and allows the user to draw on the canvas. I've looked over another StackOverflow post with the same problem but no relevant answers. I know that the cause of the problem is that the canvas is stretched from it's standard proportion of 300 x 150 and is drawing at the correct position mathematically but not physically. How do I fix this?

Drawing Dynamic Gradients in HTML5 Canvas

I'm working on an art app and I want to be able to draw a gradient as a color. For example, if I keep drawing in a straight line or in circles, I want the gradient to repeat itself over and over. Right now the gradient is isolated to one side of the screen when I draw, but I would like to be able to draw with the gradient anywhere.
I have included the drawing function and color variable for reference.
HTML
<canvas id="canvas"></canvas>
JS
window.addEventListener('load', () => {
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
let painting = false;
var gradient = ctx.createLinearGradient(0, 0, 170, 0);
gradient.addColorStop("0", "magenta");
gradient.addColorStop("0.5", "blue");
gradient.addColorStop("1.0", "red");
function windowSize() {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function startPosition(e) {
painting = true;
draw(e);
}
function finishedPosition() {
painting = false;
ctx.beginPath();
}
function draw(e) {
if(!painting) return;
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.strokeStyle = gradient;
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
canvas.addEventListener('mousedown', startPosition);
canvas.addEventListener('mouseup', finishedPosition);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('touchstart', startPosition);
canvas.addEventListener('touchend', finishedPosition);
canvas.addEventListener('touchmove', draw);
window.addEventListener('resize', windowSize);
});
As I understand, you are trying to make a repeating-linear-gradient pattern.
It's supported in css, but not yet in the canvas gradient.
If all what you want to archive is a drawing like in your example, I will suggest you to do th following:
add a css gradient as a background and cover all unused space in white.
cover all the space in white
set the ctx.globalCompositeOperation = 'destination-out'; (so it will clean the drawing instead of draw)
Like this:
window.addEventListener('load', () => {
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
let painting = false;
function windowSize() {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function startPosition(e) {
painting = true;
draw(e);
}
function finishedPosition() {
painting = false;
ctx.beginPath();
}
function draw(e) {
if(!painting) return;
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
canvas.addEventListener('mousedown', startPosition);
canvas.addEventListener('mouseup', finishedPosition);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('touchstart', startPosition);
canvas.addEventListener('touchend', finishedPosition);
canvas.addEventListener('touchmove', draw);
window.addEventListener('resize', windowSize);
});
canvas {
background: repeating-linear-gradient(to right, magenta, blue, red, magenta) repeat-x;
background-size: 50px 100%;
}
<canvas id="canvas"></canvas>
--- EDIT ---
Also, you can use different (single) color at each event, instead of use gradient, and change the hue over time.
It will produce very different result. not sure what you like more..
Will be looking like that:
window.addEventListener('load', () => {
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
let colorIdx = 0;
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
let painting = false;
function getColor() {
colorIdx += 5;
return `hsl(${colorIdx}, 100%, 50%)`;
}
function windowSize() {
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
}
function startPosition(e) {
painting = true;
draw(e);
}
function finishedPosition() {
painting = false;
ctx.beginPath();
}
function draw(e) {
if(!painting) return;
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.strokeStyle = getColor();
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
canvas.addEventListener('mousedown', startPosition);
canvas.addEventListener('mouseup', finishedPosition);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('touchstart', startPosition);
canvas.addEventListener('touchend', finishedPosition);
canvas.addEventListener('touchmove', draw);
window.addEventListener('resize', windowSize);
});
<canvas id="canvas"></canvas>
Repeated gradient on 2D canvas
There are several ways to repeat a gradient. However they are all somewhat involved and will have some limitations and problems.
2 methods
The simplest is to manually repeat the color stops (see example) but suffers from floating point error
Use a pattern to repeat a gradient. Render a single repeat gradient to an offscreen canvas and use that canvas as a pattern, setting the pattern repeat appropriately. You will also have to align the gradient to the offscreen canvas axis and then set the pattern transform to match the orientation of the desired pattern.
Repeating color stops
The following function creates a gradient and adds a function to it that will create repeated color stops.
function RepeatingGradient(ctx, x1, y1, x2, y2, repeats) {
const dx = x2 - x1, dy = y2 - y1;
const gradient = ctx.createLinearGradient(x1, y1, x1 + dx * repeats, y1 + dy * repeats);
return Object.assign(gradient, {
addRepeatColorStop(pos, color) {
var i = 0;
const step = 1 / repeats;
const offset = pos / repeats;
while (i < repeats) {
const p = (i++) * step + offset;
// Rounding error may cause exception so check that p is not greater than 1
gradient.addColorStop(p > 1 ? 1 : p, color)
}
}
});
}
Usage
RepeatingLinearGradient(ctx, x1, y1, x2, y2, repeats) It needs a 2D context ctx, then the standard linear gradient arguments, x1, y1, x2, y2 and then the number of repeats repeats.
The repeats extend the area of the gradient. Thus if the positioning arguments are 0,0,0,10 and the repeat is 10 then the gradient will cover the area 0,0,0,100
You add repeating color stops using the new function gradient.addRepeatColorStop(pos, color)
You use the resulting gradient as normal
const grad = RepeatingGradient(ctx, 0,0,0,10, 10);
grad.addRepeatColorStop(0, "#000");
grad.addRepeatColorStop(1, "#FFF");
ctx.strokeStyle = grad;
Example use
Use mouse to draw using repeated gradient.
function RepeatingGradient(ctx, x1, y1, x2, y2, repeats) {
const dx = x2 - x1;
const dy = y2 - y1;
const gradient = ctx.createLinearGradient(x1, y1, x1 + dx * repeats, y1 + dy * repeats);
return Object.assign(gradient, {
addRepeatColorStop(pos, color) {
var i = 0;
const step = 1 / repeats, offset = pos / repeats;
while (i < repeats) {
const p = (i++) * step + offset;
gradient.addColorStop(p > 1 ? 1 : p, color);
}
}
});
}
const lineWidth = 20;
const ctx = canvas.getContext('2d');
canvas.height = innerHeight;
canvas.width = innerWidth;
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
const gradient = RepeatingGradient(ctx, 0, 0, 80, 80, 20);
gradient.addRepeatColorStop(0, "#F00");
gradient.addRepeatColorStop(0.25, "#FF0");
gradient.addRepeatColorStop(0.5, "#0F0");
gradient.addRepeatColorStop(0.75, "#FF0");
gradient.addRepeatColorStop(1, "#F00");
ctx.strokeStyle = gradient;
const mouse = {x : 0, y : 0, ox: 0, oy: 0, button : false, updateFunc: undefined}
mouse.updateFunc = function draw() {
if (mouse.button) {
ctx.beginPath();
ctx.lineTo(mouse.ox, mouse.oy);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
}
}
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.ox = mouse.x;
mouse.oy = mouse.y;
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.updateFunc && mouse.updateFunc();
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Click drag mouse to draw.
Notes
The repeat is not infinite. You need to ensure that you cover all of the area you want painted.
Repeats will reduce performance by a small amount depending on the number of repeats.
Gradients use FLOATS (32 bit floating point) not DOUBLES (64 bit floating point). Adding too many repeats and you will start to get some artifacts (not all repeats are identical, some stops may be out of order). Try to keep the number of repeats to the minimum needed to fit the render area. (FLOAT is the upper size, low end devices may only support lower than 32bit precision floating point)
If rounding errors start to effect the quality, try adding repeat stop ends slightly off 0 and 1
eg
gradient.addRepeatColorStop(0.01, "#F00"); // slightly above
gradient.addRepeatColorStop(0.99, "#F0F"); // slightly below
For the best results try to have the first and last position in the repeat match each other.
eg
gradient.addRepeatColorStop(0, "#F00"); // start
gradient.addRepeatColorStop(0.5, "#0F0"); // mid
gradient.addRepeatColorStop(1, "#F00"); // Match start
Using pattern to repeat gradients
The next method uses a pattern. Warning calling this function too often can result in a out of memory exception (gecko)
The function
function RepeatingLinearGradient(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const length = (dx * dx + dy * dy) ** 0.5 | 0;
const can = document.createElement("canvas"); // support for OffscreenCanvas() limited
can.width = length;
can.height = 1;
const ctxP = can.getContext("2d");
const nx = dx / length;
const ny = dy / length;
const matrix = new DOMMatrixReadOnly([nx, ny, -ny, nx ,x1, y1]);
const gradient = ctxP.createLinearGradient(0, 0, length, 0);
var dirty = true;
function update() {
ctxP.fillStyle = gradient;
ctxP.fillRect(0,0,length, 1);
var pattern;
Object.assign(pattern = ctxP.createPattern(can, "repeat"), {
addColorStop(pos, color) {
gradient.addColorStop(pos, color);
return update();
}
});
pattern.setTransform(matrix);
return pattern;
}
return update();
}
The function creates an off screen canvas and renders an axis align gradient to it. Each time you add a color stop a new pattern is created and returned.
To align the pattern with the desired gradient the patterns transform is set to match the gradients orientation.
Usage
Similar to a normal gradient but returns a pattern. Arguments are two coordinate pairs x1, y1, x2, y2
IMPORTANT As patterns are not live you must reassign the gradient every time you make a change (eg add color stop)
var gradient = RepeatingLinearGradient(0, 0, 80, 80);
/* MUST!!! reassign */
gradient = gradient.addColorStop(0, "#000"); // MUST!!! reassign
gradient = gradient.addColorStop(1, "#FFF"); // MUST!!! reassign
ctx.strokeStyle = gradient;
Example
Use mouse to draw
function RepeatingGradient(x1, y1, x2, y2) {
const dx = x2 - x1;
const dy = y2 - y1;
const length = (dx * dx + dy * dy) ** 0.5 | 0;
const can = document.createElement("canvas");
can.width = length;
can.height = 1;
const ctxP = can.getContext("2d");
const nx = dx / length;
const ny = dy / length;
const matrix = new DOMMatrixReadOnly([nx, ny, -ny, nx ,x1, y1]);
const gradient = ctxP.createLinearGradient(0, 0, length, 0);
var dirty = true;
function update() {
ctxP.fillStyle = gradient;
ctxP.fillRect(0,0,length, 1);
var pattern;
Object.assign(pattern = ctxP.createPattern(can, "repeat"), {
addColorStop(pos, color) {
gradient.addColorStop(pos, color);
return update();
}
});
pattern.setTransform(matrix);
return pattern;
}
return update();
}
const lineWidth = 20;
const ctx = canvas.getContext('2d');
canvas.height = innerHeight;
canvas.width = innerWidth;
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
var gradient = RepeatingGradient(0, 0, 40, 20);
gradient = gradient.addColorStop(0, "#F00");
gradient = gradient.addColorStop(0.25, "#FF0");
gradient = gradient.addColorStop(0.5, "#0F0");
gradient = gradient.addColorStop(0.75, "#FF0");
gradient = gradient.addColorStop(1, "#F00");
ctx.strokeStyle = gradient;
const mouse = {x : 0, y : 0, ox: 0, oy: 0, button : false, updateFunc: undefined}
mouse.updateFunc = function draw() {
if (mouse.button) {
ctx.beginPath();
ctx.lineTo(mouse.ox, mouse.oy);
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
}
}
function mouseEvents(e){
const bounds = canvas.getBoundingClientRect();
mouse.ox = mouse.x;
mouse.oy = mouse.y;
mouse.x = e.pageX - bounds.left - scrollX;
mouse.y = e.pageY - bounds.top - scrollY;
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
mouse.updateFunc && mouse.updateFunc();
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
canvas { position : absolute; top : 0px; left : 0px; }
<canvas id="canvas"></canvas>
Click drag mouse to draw.
Notes
Does not suffer the same floating point issues as the first method.
Is slower when creating patterns and uses more memory.
Can throw memory error if creating too often or adding stops too often.
Repeats are infinite, You can not limit the number of repeats.
Uses DOMMatrixReadOnly this may not be supported by all browsers.

HTML5 Canvas draw lines in a circle and move with music?

I am able to draw lines around a circle. I have the basic implementation of the AudioContext API setup.
The problem I am facing is when calling lineTo the line will only grow but not shrink. I am inspired by this https://www.kkhaydarov.com/audio-visualizer/. I am translating this code over into https://codesandbox.io/s/hungry-tereshkova-1pf0c?runonclick=1&file=/src/Visualizer.js:713-725 which is a React.js version.
If you run that code you will see the music play, then the bars will grow once, and then they stick. They refuse to shrink then grow to the beat.
I am not sure where I am going wrong or what I am missing in that code. It seems pretty similar to the other code in the example.
Here is the full code for the Visualizer component.
import React, { useEffect, useRef } from "react";
let frequencyArray = [];
let analyser;
const Visualizer = () => {
const canvasRef = useRef(null);
const requestRef = useRef(null);
useEffect(() => {
initAudio();
requestRef.current = requestAnimationFrame(drawCanvas);
return () => cancelAnimationFrame(requestRef.current);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const initAudio = () => {
const audio = new Audio();
audio.src =
"https://s3.us-west-2.amazonaws.com/storycreator.uploads/ck9kpb5ss0xf90132mgf8z893?client_id=d8976b195733c213f3ead34a2d95d1c1";
audio.crossOrigin = "anonymous";
audio.load();
const context = new (window.AudioContext || window.webkitAudioContext)();
analyser = context.createAnalyser();
const source = context.createMediaElementSource(audio);
source.connect(analyser);
analyser.connect(context.destination);
frequencyArray = new Uint8Array(analyser.frequencyBinCount);
audio.play();
};
// draw the whole thing
const drawCanvas = () => {
if (canvasRef.current) {
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
const radius = 200;
const bars = 100;
drawCircle(canvas, ctx, radius);
analyser.getByteFrequencyData(frequencyArray);
for (var i = 0; i < bars; i++) {
const height = frequencyArray[i] * 0.3;
drawLine(
{
i,
bars,
height,
radius
},
canvas,
ctx
);
}
requestRef.current = requestAnimationFrame(drawCanvas);
}
};
// draw the main circle
const drawCircle = (canvas, ctx, radius) => {
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
ctx.save();
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = "white";
ctx.fill();
ctx.strokeStyle = "#dddddd";
ctx.lineWidth = 5;
ctx.stroke();
ctx.restore();
};
// dray lines around the circle
const drawLine = (opts, canvas, ctx) => {
const { i, radius, bars, height } = opts;
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const lineWidth = 10;
const rads = (Math.PI * 2) / bars;
const x = centerX + Math.cos(rads * i) * (radius + lineWidth);
const y = centerY + Math.sin(rads * i) * (radius + lineWidth);
const endX = centerX + Math.cos(rads * i) * (radius + height);
const endY = centerY + Math.sin(rads * i) * (radius + height);
// draw the bar
ctx.strokeStyle = "#ddd";
ctx.lineWidth = lineWidth;
ctx.lineCap = "round";
ctx.beginPath();
ctx.moveTo(x, y);
ctx.lineTo(endX, endY);
ctx.stroke();
};
return (
<canvas
ref={canvasRef}
style={{ background: "#f5f5f5" }}
width={window.innerWidth}
height={window.innerHeight}
/>
);
};
export default Visualizer;
You just missed a clearRect in your code...
Without that we see the lines grow only because any following shorter line does not overwrite the previous one, they are still getting drawn just we do not see it.
here is the working code:
https://codesandbox.io/s/dry-cdn-ghu4m?file=/src/Visualizer.js:1247-1276
I hardcoded a ctx.clearRect(0,0, 1000,1000) just to show you that it works, but you should use the canvas dimensions there, everything else looks good.
Only recommendation will be to somehow move:
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
somewhere global outside the drawCanvas function,
those do not change on every run, will be nice to set them just once.

How to crop canvas image by path, not area

Im making a painting tool, and one of the feature is showing cropped image of the drawn path.
The path I have drawn(image)
For example in above the picture, the white colored path indicates what I have drawn, just like a painting tool.
Cropped image
And here is the cropped image of the path. If you look at the picture, you can see that it crops the image as if the path is closed and therefore it crops the image "area" not the path.
and here is the code
function crop({ image, points }) {
return Observable.create(observer => {
const { width, height } = getImageSize(image);
const canvas = document.createElement('canvas') as HTMLCanvasElement;
const context = canvas.getContext('2d');
canvas.width = width;
canvas.height = height;
context.beginPath();
points.forEach(([x, y], idx) => {
if (idx === 0) {
context.moveTo(x, y);
} else {
context.lineTo(x, y);
}
});
context.clip();
context.drawImage(image);
...etc
}
The crop function receives points which is consisted [x coordinate, y coordinate][ ] of the drawn path.
Is there an way to show image only the path that I've painted?
That's more what is generally called a mask then, but note that both for the current clip or for the mask you want to attain, the best is to use compositing.
Canvas context has various compositing options, allowing you to generate complex compositions, from pixels's alpha value.
const ctx = canvas.getContext('2d');
const pathes = [[]];
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';
function begin() {
canvas.width = this.width;
canvas.height = this.height;
ctx.lineWidth = 10;
addEventListener('mousemove', onmousemove);
addEventListener('mousedown', onmousedown);
addEventListener('mouseup', onmouseup);
anim();
ctx.fillText("Use your mouse to draw a path", 20,50)
}
function anim() {
requestAnimationFrame(anim);
if(dirty) draw();
dirty = false;
}
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bg, 0, 0);
ctx.beginPath();
pathes.forEach(path => {
if(!path.length) return;
ctx.moveTo(path[0].x, path[0].y);
path.forEach(pt => {
ctx.lineTo(pt.x, pt.y);
});
});
// old drawings will remain on where new drawings will be
ctx.globalCompositeOperation = 'destination-in';
ctx.stroke();
// reset
ctx.globalCompositeOperation = 'source-over';
}
function onmousemove(evt) {
if(!down) return;
const rect = canvas.getBoundingClientRect();
pathes[pathes.length - 1].push({
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
});
dirty = true;
}
function onmousedown(evt) {
down = true;
}
function onmouseup(evt) {
down = false;
pathes.push([]);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>
Don't hesitate to look at all the compositing options, various cases will require different options, for instance if you need to draw multiple paths, you may prefer to render first your paths and then keep your image only where you did already drawn, using the source-atop option:
const ctx = canvas.getContext('2d');
const pathes = [[]];
pathes[0].lineWidth = (Math.random() * 20) + 0.2;
let down = false;
let dirty = false;
const bg = new Image();
bg.onload = begin;
bg.src = 'https://upload.wikimedia.org/wikipedia/commons/thumb/e/ec/Serene_Sunset_%2826908986301%29.jpg/320px-Serene_Sunset_%2826908986301%29.jpg';
function begin() {
canvas.width = this.width;
canvas.height = this.height;
addEventListener('mousemove', onmousemove);
addEventListener('mousedown', onmousedown);
addEventListener('mouseup', onmouseup);
anim();
ctx.fillText("Use your mouse to draw a path", 20,50)
}
function anim() {
requestAnimationFrame(anim);
if(dirty) draw();
dirty = false;
}
function draw() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
pathes.forEach(path => {
if(!path.length) return;
ctx.beginPath();
ctx.lineWidth = path.lineWidth;
ctx.moveTo(path[0].x, path[0].y);
path.forEach(pt => {
ctx.lineTo(pt.x, pt.y);
});
ctx.stroke();
});
// new drawings will appear on where old drawings were
ctx.globalCompositeOperation = 'source-atop';
ctx.drawImage(bg, 0, 0);
// reset
ctx.globalCompositeOperation = 'source-over';
}
function onmousemove(evt) {
if(!down) return;
const rect = canvas.getBoundingClientRect();
pathes[pathes.length - 1].push({
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
});
dirty = true;
}
function onmousedown(evt) {
down = true;
}
function onmouseup(evt) {
down = false;
const path = [];
path.lineWidth = (Math.random() * 18) + 2;
pathes.push(path);
}
canvas {border: 1px solid}
<canvas id="canvas"></canvas>
And also remember that you can very well have canvases that you won't append to the document that you can use as layers to generate really complex compositions. (drawImage() does accept a <canvas> as source).

Categories