How to not erase background in canvas JS? - javascript

So I made this canvas on which you can paint on. The problem is that when you erase your drawings it will also erase the background.
// SETTING ALL VARIABLES
var isMouseDown=false;
var canvas = document.createElement('canvas');
var body = document.getElementsByTagName("body")[0];
var ctx = canvas.getContext('2d');
var linesArray = [];
currentSize = 5;
var currentColor = "rgb(200,20,100)";
var currentBg = "white";
let newImage = new Image();
newImage.src = 'https://www.arnoldvanhooft.nl/wp-content/uploads/2019/06/ja-knop.png'
// INITIAL LAUNCH
newImage.onload = () => {
ctx.drawImage(newImage, 0, 0, 500, 500);
}
createCanvas();
// BUTTON EVENT HANDLERS
document.getElementById('canvasUpdate').addEventListener('click', function() {
createCanvas();
redraw();
});
document.getElementById('colorpicker').addEventListener('change', function() {
currentColor = this.value;
});
document.getElementById('bgcolorpicker').addEventListener('change', function() {
ctx.fillStyle = this.value;
ctx.fillRect(0, 0, canvas.width, canvas.height);
redraw();
currentBg = ctx.fillStyle;
});
document.getElementById('controlSize').addEventListener('change', function() {
currentSize = this.value;
document.getElementById("showSize").innerHTML = this.value;
});
document.getElementById('saveToImage').addEventListener('click', function() {
downloadCanvas(this, 'canvas', 'masterpiece.png');
}, false);
document.getElementById('eraser').addEventListener('click', eraser);
document.getElementById('clear').addEventListener('click', createCanvas);
document.getElementById('save').addEventListener('click', save);
document.getElementById('load').addEventListener('click', load);
document.getElementById('clearCache').addEventListener('click', function() {
localStorage.removeItem("savedCanvas");
linesArray = [];
console.log("Cache cleared!");
});
// REDRAW
function redraw() {
for (var i = 1; i < linesArray.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
}
// DRAWING EVENT HANDLERS
canvas.addEventListener('mousedown', function() {mousedown(canvas, event);});
canvas.addEventListener('mousemove',function() {mousemove(canvas, event);});
canvas.addEventListener('mouseup',mouseup);
// CREATE CANVAS
function createCanvas() {
canvas.id = "canvas";
canvas.width = parseInt(document.getElementById("sizeX").value);
canvas.height = parseInt(document.getElementById("sizeY").value);
canvas.style.zIndex = 8;
canvas.style.position = "absolute";
canvas.style.border = "1px solid";
ctx.fillStyle = currentBg;
ctx.fillRect(0, 0, canvas.width, canvas.height);
body.appendChild(canvas);
}
// DOWNLOAD CANVAS
function downloadCanvas(link, canvas, filename) {
link.href = document.getElementById(canvas).toDataURL();
link.download = filename;
}
// SAVE FUNCTION
function save() {
localStorage.removeItem("savedCanvas");
localStorage.setItem("savedCanvas", JSON.stringify(linesArray));
console.log("Saved canvas!");
}
// LOAD FUNCTION
function load() {
if (localStorage.getItem("savedCanvas") != null) {
linesArray = JSON.parse(localStorage.savedCanvas);
var lines = JSON.parse(localStorage.getItem("savedCanvas"));
for (var i = 1; i < lines.length; i++) {
ctx.beginPath();
ctx.moveTo(linesArray[i-1].x, linesArray[i-1].y);
ctx.lineWidth = linesArray[i].size;
ctx.lineCap = "round";
ctx.strokeStyle = linesArray[i].color;
ctx.lineTo(linesArray[i].x, linesArray[i].y);
ctx.stroke();
}
console.log("Canvas loaded.");
}
else {
console.log("No canvas in memory!");
}
}
// ERASER HANDLING
function eraser() {
currentSize = 50;
currentColor = ctx.fillStyle
}
// GET MOUSE POSITION
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
// ON MOUSE DOWN
function mousedown(canvas, evt) {
var mousePos = getMousePos(canvas, evt);
isMouseDown=true
var currentPosition = getMousePos(canvas, evt);
ctx.moveTo(currentPosition.x, currentPosition.y)
ctx.beginPath();
ctx.lineWidth = currentSize;
ctx.lineCap = "round";
ctx.strokeStyle = currentColor;
}
// ON MOUSE MOVE
function mousemove(canvas, evt) {
if(isMouseDown){
var currentPosition = getMousePos(canvas, evt);
ctx.lineTo(currentPosition.x, currentPosition.y)
ctx.stroke();
store(currentPosition.x, currentPosition.y, currentSize, currentColor);
}
}
// STORE DATA
function store(x, y, s, c) {
var line = {
"x": x,
"y": y,
"size": s,
"color": c
}
linesArray.push(line);
}
// ON MOUSE UP
function mouseup() {
isMouseDown=false
store()
}
.colorButtons {
display: block;
margin: 20px 0;
}
canvas {
cursor: crosshair;
}
div#sidebar {
position: absolute;
left: 0;
width: 150px;
padding: 20px 20px;
top: 0;
}
canvas#canvas {
left: 150px;
top: 45px;
}
.btn {
margin-bottom: 10px;
width: 100%;
}
input {
width: 100%;
margin-bottom: 10px;
}
.input-group {
margin-bottom: 10px;
}
.toolsButtons .btn {
width: 48%;
}
.sizeButtons .btn {
width: 48%;
}
.colorpicker {
background: transparent;
height: 40px;
}
<!-- using Bootstrap CSS because lazy to write 3 classes -->
<body>
<div id="sidebar">
<div class="colorButtons">
<h3>Colour</h3>
<input type="color" id="colorpicker" value="#c81464" class="colorpicker">
</div>
<div class="colorButtons">
<h3>Bg Color</h3>
<input type="color" value="#ffffff" id="bgcolorpicker" class="colorpicker">
</div>
<div class="toolsButtons">
<h3>Tools</h3>
<button id="eraser" class="btn btn-default">eraser</span></button>
<button id="clear" class="btn btn-danger"> <span class="glyphicon glyphicon-repeat" aria-hidden="true"></span></button>
</div>
<div class="buttonSize">
<h3>Size (<span id="showSize">5</span>)</h3>
<input type="range" min="1" max="50" value="5" step="1" id="controlSize">
</div>
<div class="canvasSize">
<h3>Canvas</h3>
<div class="input-group">
<span class="input-group-addon">X</span>
<input type="number" id="sizeX" class="form-control" placeholder="sizeX" value="800" class="size">
</div>
<div class="input-group">
<span class="input-group-addon">Y</span>
<input type="number" id="sizeY" class="form-control" placeholder="sizeY" value="800" class="size">
</div>
<input type="button" class="updateSize btn btn-success" value="Update" id="canvasUpdate">
</div>
<div class="Storage">
<h3>Storage</h3>
<input type="button" value="Save" class="btn btn-warning" id="save">
<input type="button" value="Load" class="btn btn-warning" id="load">
<input type="button" value="Clear" class="btn btn-warning" id="clearCache">
</div>
<div class="extra">
<h3>Extra</h3>
<a id="saveToImage" class="btn btn-warning">Download</a>
</div>
</div>
</body>
I have tried by adding the photo in a different way but that way it wouldn't be saved the right way. I also have tried changing layers with CSS and index but that also didn't work

Using layers
A canvas drawing app can use many canvases to define layers. Layers can include things like backgrounds, drawing layers, composite layers (multiply, screen, etc) and much more. Much the same as layers are used in apps like photoshop.
A bonus when using layers is that the immediate drawing state can be displayed without affecting the existing layers, as you can draw the pen on the output layer when the mouse button is not down. (see example)
To get the most from canvas layers you should become familiar with the many ctx.globalCompositeOperation modes.
The example uses the following ctx.globalCompositeOperation modes
"copy" copies pixels from source to destination including transparent pixels.
"source-over" (used in example draw mode) The default drawing mode. Copies pixels ignoring transparent pixels and blending semi transparent pixels.
"destination-out" (used in example erase mode) Removes pixels from the destination canvas where you draw opaque pixels, and partially removes pixels where you draw semi transparent pixels.
Performance
Even lowend devices can handle many canvas layers easily as long as you ensure that the canvas resolution does not exceed the device display size by many factors as performance is regulated by the availability of GPU RAM
You may be tempted to have the DOM handle the layer composition. It turns out that using the CanvasRenderingContext2D API to do layering is more efficient than letting the DOM handle it
Example
Below is a very basic drawing example. It uses 2 canvas layers, one for the background, and one for the drawing layer.
The background is loaded and then drawn to scale on the bg canvas.
When the mouse button is down the update function draws or erases to/from the drawing layer.
A 3rd canvas is used to show the result. This canvas is added to the DOM and the update function renders the layers to it as needed.
To save the result of the layers you can download the content of the 3rd canvas, or create a new canvas (if the display canvas size does not match the drawing size), draw the layers to it, and download its content.
Useage: Use mouse (left click) to draw / erase on drawing layer. Use button to toggle drawing mode (Draw / Erase)
;(()=>{
setTimeout(start, 0);
var ctx1, ctx2, ctx3;
const SIZE = 180;
const PEN_SIZE = 30;
function start() {
const button = tag("button", {textContent: "Draw", title: "Toggle erase / draw mode", className: "floatBtn"});
const canProps = {width: SIZE, height: SIZE};
ctx1 = tag("canvas", canProps).getContext("2d"); // BG layer
ctx2 = tag("canvas", canProps).getContext("2d"); // drawing layer
ctx3 = tag("canvas", canProps).getContext("2d"); // display canvas context
ctx2.lineWidth = ctx3.lineWidth = PEN_SIZE;
ctx2.lineCap = ctx3.lineCap = "round";
ctx2.lineJoin = ctx3.lineJoin = "round";
ctx2.strokeStyle = ctx3.strokeStyle = "BLUE";
append(BODY, ctx3.canvas, button);
// Load BG image and draw on bg canvas when loaded. Note bg is
// scaled to fit 180 by 180 canvas
const bgImg = new Image;
bgImg.src = "https://i.stack.imgur.com/C7qq2.png?s=256&g=1";
listener(bgImg, "load", () => (ctx1.drawImage(bgImg, 0, 0, 180, 180), mouse.update = true), {once: true});
listener(button, "click", () => {
mouse.draw = !mouse.draw; // Toggle drawing mode
button.textContent = mouse.draw ? "Draw" : "Erase";
});
mouse.update = true;
update();
}
function update() {
requestAnimationFrame(update)
if (!mouse.update) { return }
ctx3.globalCompositeOperation = "copy"; // to draw bg image
ctx3.drawImage(ctx1.canvas, 0 , 0);
if (mouse.lastX !== undefined) { // Avoid line from zero when mouse first over body
ctx3.globalCompositeOperation = "source-over"; // to draw drawing layer
if (mouse.button) { // draw on drawing layer if mouse down
ctx2.globalCompositeOperation = mouse.draw ? "source-over" : "destination-out";
ctx2.beginPath();
ctx2.lineTo(mouse.lastX, mouse.lastY);
ctx2.lineTo(mouse.x, mouse.y + 0.01); // Small 100th px offset
// ensures line is drawn
ctx2.stroke();
}
ctx3.drawImage(ctx2.canvas, 0 , 0);
if (!mouse.button) {
ctx3.strokeStyle = mouse.draw ? "BLUE" : "RED";
ctx3.beginPath();
ctx3.lineTo(mouse.lastX, mouse.lastY);
ctx3.lineTo(mouse.x, mouse.y + 0.01);
ctx3.stroke();
}
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.update = false;
}
const TAU = Math.PI * 2;
const DOC = document, BODY = DOC.body, assign = Object.assign;
const isArr = Array.isArray;
const tag = (tag, props = {}) => assign(DOC.createElement(tag), props);
const append = (el, ...sibs) => sibs.reduce((p, sib) => ((isArr(sib) ? append(p, ...sib) : p.appendChild(sib)), p), el);
const listener = (qe, name, call, opt = {}) => (qe.addEventListener(name, call, opt), qe);
const mouse = {x: 0, y: 0, button: false, lastX: undefined, lastY: undefined, draw: true, update: true}
function mouseEvents(e) {
mouse.update = true;
mouse.x = e.pageX;
mouse.y = e.pageY;
if (mouse.lastX === undefined) {
mouse.lastX = mouse.x;
mouse.lastY = mouse.y;
}
mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
}
["down", "up", "move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents));
})();
canvas { position: absolute; top: 0px; left: 0px; cursor: crosshair}
.floatBtn { position : absolute; top: 0px; left: 180px; cursor: pointer}

Related

adding image inside html canvas using javascript not working

i am trying to create an image canvas where user can zoom into the image, the code which i got from here enter link description here, now i tried to add image inside it and i did the following code:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base(context);
}
function make_base(context) {
var base_image = new Image();
base_image.src = 'https://www.gstatic.com/webp/gallery3/1.sm.png';
base_image.onload = function() {
context.drawImage(base_image, 0, 0);
}
}
window.onload = function() {
var canvas = document.getElementById("myCanvas");
var translatePos = {
x: canvas.width / 2,
y: canvas.height / 2
};
var scale = 1.0;
var scaleMultiplier = 0.8;
var startDragOffset = {};
var mouseDown = false;
// add button event listeners
document.getElementById("plus").addEventListener("click", function() {
scale /= scaleMultiplier;
draw(scale, translatePos);
}, false);
document.getElementById("minus").addEventListener("click", function() {
scale *= scaleMultiplier;
draw(scale, translatePos);
}, false);
// add event listeners to handle screen drag
canvas.addEventListener("mousedown", function(evt) {
mouseDown = true;
startDragOffset.x = evt.clientX - translatePos.x;
startDragOffset.y = evt.clientY - translatePos.y;
});
canvas.addEventListener("mouseup", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mouseover", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mouseout", function(evt) {
mouseDown = false;
});
canvas.addEventListener("mousemove", function(evt) {
if (mouseDown) {
translatePos.x = evt.clientX - startDragOffset.x;
translatePos.y = evt.clientY - startDragOffset.y;
draw(scale, translatePos);
}
});
draw(scale, translatePos);
};
jQuery(document).ready(function() {
$("#wrapper").mouseover(function(e) {
$('#status').html(e.pageX + ', ' + e.pageY);
});
})
body {
margin: 0px;
padding: 0px;
}
#wrapper {
position: relative;
border: 1px solid #9C9898;
width: 578px;
height: 200px;
}
#buttonWrapper {
position: absolute;
width: 30px;
top: 2px;
right: 2px;
}
input[type="button"] {
padding: 5px;
width: 30px;
margin: 0px 0px 2px 0px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<body onmousedown="return false;">
<div id="wrapper">
<canvas id="myCanvas" width="578" height="200">
</canvas>
<div id="buttonWrapper">
<input type="button" id="plus" value="+"><input type="button" id="minus" value="-">
</div>
</div>
<h2 id="status">
0, 0
</h2>
</body>
however the image is not getting displayed inside the canvas, can anyone please tell me what could be wrong in here, thanks in advance
Your draw function never actually draws to the canvas. You get the canvas and context in the first 2 lines, but you need to call drawImage with the image to actually add it to the canvas itself.
I suspect you want to be calling make_base inside it like so:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base();
}
You also need to have the context in the same scope as you use it. At the moment, the variable context only exists inside the draw function and not the make_base function, so you can't access it from inside make_base.
You can pass it as a variable like so:
function draw(scale, translatePos) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
make_base(context);
}
function make_base(context) {
var base_image = new Image();
base_image.src = 'a2.jpg';
base_image.onload = function() {
context.drawImage(base_image, 0, 0);
}
}
Every time you want to change anything on an HTML canvas you need to call draw functions to change what's there.

Issue related to event listener on canvas object within a canvas & additional issue

Main Issue:
l essentially want to figure the issue with my event listener as it is aligning with the canvas object, which is the image '', in the middle of the canvas however, the Y areas below it are still clickable and the X areas on the right of it are still clickable.
l would like to eliminate this issue, which l believe is being caused by my IF statement and the DRAWIMAGE conditions, in relation to my canvas. There is a reproducible demo, fullscreen/expand it to see.
Another issue:
Another thing to note, which would be much appreciated, is the canvas object not truly sticking in one position on the canvas when you resize the browser window. It simply moves off into a different direction even though it should be stuck in one area of the canvas no matter what size my browser's window is - meaning that the canvas object somehow needs to dynamically resize along with how my browser resize + the event listener needs to see it. Again this would be highly appreciated as l really want to understand the error of my logic as l might using the wrong coordinate system,i don't really know :/
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var the_button = document.getElementById("the_button");
var the_background = document.getElementById("the_background");
var button_imageX = 600;
var button_imageY = 390;
window.onload = function() {
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
initialize();
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
}
function drawButton() {
/* l belive this is partly responsible aswell for the issue */
ctx.drawImage(the_button, button_imageX, canvas.height - button_imageY, 170, 100);
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
redraw();
}
the_button_function = (paramater1) => {
/* Problem lies here in the IF statement aswell, that's my guess */
if ((paramater1.x > (canvas.width - button_imageX)) && (paramater1.x < canvas.width) && (paramater1.y > (canvas.height - button_imageY)) && (paramater1.y < canvas.height)) {
alert("<Button>")
}
}
canvas.addEventListener('click', (e) => the_button_function(e));
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
display: block;
}
<html>
<canvas id="c"></canvas>
<img style="display: none;" id="the_button" src="https://i.imgur.com/wO7Wc2w.png" />
<img style="display: none;" id="the_background" src="https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg" />
</html>
To stop clicks responding to the right of and below the button, take into account the width and height of the button!
Fixing this, and knowing where the image was previously drawn on the canvas should fix the second issue. To debug the problem the snippet code below replaces
button_imageX with button_imageLeft - how many pixels from canvas left to draw the image.
button_imageY with button_imageBottom - how many pixels from canvas bottom to draw the top of the image. (This seemed to be how the posted code was positioning the button in the y direction.)
[image_bottonLeft and image_buttonBottom values were modified for seeing results on Stack Overflow.]
And introduced
button_offsetX - x position of where the left hand side of the button was last drawn
button_offsetY - y position of where the top of the button was last drawn
button_imageWidth and button_imageHeight values for button height and width, replacing hard coded values function drawButton.
var canvas = document.getElementById("c");
var ctx = canvas.getContext("2d");
var the_button = document.getElementById("the_button");
var the_background = document.getElementById("the_background");
var button_imageLeft = 100;
var button_imageBottom = 150;
var button_imageWidth = 170;
var button_imageHeight = 100;
var button_offsetX, button_offsetY;
window.onload = function() {
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
initialize();
function initialize() {
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
}
function drawButton() {
button_offsetX = button_imageLeft;
button_offsetY = canvas.height - button_imageBottom;
ctx.drawImage(the_button, button_offsetX, button_offsetY, button_imageWidth, button_imageHeight);
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(the_background, 0, 0, canvas.width, canvas.height);
drawButton();
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
redraw();
}
the_button_function = (event) => {
const {x,y} = event;
if( (x >= button_offsetX && x < (button_offsetX+button_imageWidth))
&& (y >= button_offsetY && y < (button_offsetY+button_imageHeight))) {
alert("<Button>")
}
}
canvas.addEventListener('click', (e) => the_button_function(e));
html,
body {
width: 100%;
height: 100%;
margin: 0px;
border: 0;
overflow: hidden;
display: block;
}
<canvas id="c"></canvas>
<img style="display: none;" id="the_button" src="https://i.imgur.com/wO7Wc2w.png" />
<img style="display: none;" id="the_background" src="https://img.freepik.com/free-photo/hand-painted-watercolor-background-with-sky-clouds-shape_24972-1095.jpg?size=626&ext=jpg" />

A way to bind the mousedown event on magnifier to Raphael Canvas

I have been working with Raphael to create guides on an image using the RaphaelJs Canvas. Have been able to create the guides successfully using RaphaelJS. The problem is that I need the magnifier as well when the user is creating the guides. When I enable the magnifier I am unable to bind the mousedown event to the Raphael Canvas to draw the guides on the Canvas.
So I have been use the on() method to bind the mousedown vent but have not been able to do so. Some help on this would be really appreciated.
I have created a JsFiddle for the problem that I am facing. Please have a look https://jsfiddle.net/manikbajaj/f4knwq7m/2/
I would want the guides to be drawn even when the magnifier is on. You can disable the magnifier in the fiddle to see that I am able to draw the guides.
Pasting the code here as well for a review
Here is the HTML
<div class="container"
<div class="row">
<div class="col-12 win-crosshair-over">
<div class="magnify" id="magnifier">
<img
id="magnifyImage"
src="https://i.imgur.com/ZAqoag5.jpg"
alt="Sample image"
class="zoom"
width="500"
height="300"
data-magnify-src="https://i.imgur.com/ZAqoag5.jpg"
style="display: block;"/>
<div id="canvasGame"></div>
</div>
</div>
</div>
</div>
Js
// Try to Disable the Magnifier And Raphale Will Work
$(document).ready(function(){
$('#magnifyImage').magnify({
speed: 300,
limitBounds: true,
afterLoad: function() {
}
});
});
// JS For Raphael
function Line(startX, startY, endX, endY, color, raphael){
var start = {
x: startX,
y: startY,
};
var end = {
ex: endX,
ey: endY
};
var color = color;
var getEndX = function() {
return end.ex;
};
var getEndy = function() {
return end.ey;
};
var getPath = function() {
return "M" + start.x + "," + start.y + "L" + getEndX() + "," + getEndy();
};
var redraw = function() {
node.attr("path", getPath());
};
var node = raphael.path(getPath());
node.attr({
'fill': color,
'stroke': color,
"stroke-width":"2"
});
return {
updateStart: function(x, y) {
start.x = x;
start.y = y;
redraw();
return this;
},
updateEnd: function(x, y) {
end.ex = x;
end.ey = y;
redraw();
return this;
},
clear: function() {
node.remove();
}
};
}
$(function() {
var $paper = $("#canvasGame");
var paper = Raphael(document.getElementById('canvasGame'), 895, 500);
var color = "#ff0000";
$paper.mousedown(
function(e) {
e.stopPropagation();
c = color;
x = e.offsetX;
y = e.offsetY;
line = Line(x, y, x, y, c, paper);
$paper.bind('mousemove', function(e) {
x = e.offsetX;
y = e.offsetY;
line.updateEnd(x, y);
});
});
$paper.mouseup(
function(e) {
e.stopPropagation();
$paper.unbind('mousemove');
});
});
CSS
#canvasGame {
height: 300px;
width: 500px;
background-color: rgba(255,255,255,0);
margin: 0 auto;
border: 1px black solid;
position: absolute;
z-index: 1;
cursor: crosshair;
top:0;
left: 0;
border: none;
}
.win-crosshair-over{
height: 300px;
}

Optimise HTML5 canvas

Why does html5 canvas become very slow when I draw 2000 images and more ?
How can I optimise it?
Here's a demo that I've made, disable the "safe mode" by left clicking on the canvas and start moving your mouse until you get ~2000 images drawn
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({x: event.clientX, y: event.clientY})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
var safe = true;
cnv.contextmenu(function(e) { e.preventDefault() })
cnv.mousedown(function(event) {
if(event.which == 1) safe = !safe;
if(event.which == 3) draw = []
});
function Draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
for(var i of draw) {
ctx.drawImage(img, i.x, i.y)
}
if(safe && draw.length > 300) draw = []
ctx.fillText("Images count: "+ draw.length,10, 50);
ctx.fillText("Left click to toggle the 300 images limit",10, 70);
ctx.fillText("Right click to clear canvas",10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Codepen: http://codepen.io/anon/pen/PpeNme
The simple way with the actual code :
Don't redraw all your images at this rate.
Since in your example, the images are static, you actually don't need to redraw everything every frame : Just draw the latest ones.
Also, if you've got other drawings occurring (e.g your texts), you may want to use an offscreen canvas for only the images, that you'll redraw on the onscreen canvas + other drawings.
var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";
img.onload = Draw;
var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];
$("canvas").mousemove(add)
function add(event) {
draw.push({
x: event.clientX,
y: event.clientY
})
}
canvas.width = cnv.width();
canvas.height = cnv.height();
// create an offscreen clone of our canvas for the images
var imgCan = canvas.cloneNode();
var imgCtx = imgCan.getContext('2d');
var drawn = 0; // a counter to know how much image we've to draw
var safe = true;
cnv.contextmenu(function(e) {
e.preventDefault()
})
cnv.mousedown(function(event) {
if (event.which == 1) safe = !safe;
if (event.which == 3) draw = []
});
function Draw() {
// clear the visible canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
requestAnimationFrame(Draw);
if (draw.length) { // onmy if we've got some objects to draw
for (drawn; drawn < draw.length; drawn++) { // only the latest ones
let i = draw[drawn];
// draw it on the offscreen canvas
imgCtx.drawImage(img, i.x, i.y)
}
}
// should not be needed anymore but...
if (safe && draw.length > 300) {
draw = [];
drawn = 0; // reset our counter
// clear the offscren canvas
imgCtx.clearRect(0, 0, canvas.width, canvas.height);
}
// draw the offscreen canvas on the visible one
ctx.drawImage(imgCan, 0, 0);
// do the other drawings
ctx.fillText("Images count: " + draw.length, 10, 50);
ctx.fillText("Left click to toggle the 300 images limit", 10, 70);
ctx.fillText("Right click to clear canvas", 10, 90);
}
Draw();
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
position: absolute;
}
canvas {
position: absolute;
width: 100%;
height: 100%;
z-index: 99999999999;
cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>
Now, if you need these images to be dynamic (i.e move at every frame), you may consider using imageDatas.
You can see this original post by user #Loktar, which explains how to parse your drawn image imageData, and then redraw it pixel per pixel on the visible canvas' imageData. You can also see this follow-up Q/A, which provides a color implementation of Loktar's idea.
On small images, this actually improves drastically the performances, but it has the huge inconvenient to not support alpha channel multiplication. You will only have fully transparent and fully opaque pixels. An other cons is that it may be harder to implement, but this is just your problem ;-)

Adding a link to a dynamic canvas

I have a canvas script, with a dynamic of data. I want to add a link to share the website to facebook:
https://gyazo.com/c1fd1fe956fddba27b48907dc0e9de0a
The icons are part of the image I have not generated them via canvas, now if I listen for a click for co-ords it won't work because it'll look for clicks on the first canvas part aswell.... How can I go about making those icons part of the image clickable....
Part that makes the menu:
ig.module("game.entities.gameover").requires("impact.entity", "game.entities.button-gameover").defines(function() {
var b = new ig.Timer;
EntityGameover = ig.Entity.extend({
size: {
x: 302,
y: 355
},
type: ig.Entity.TYPE.B,
animSheet: new ig.AnimationSheet("media/graphics/game/gameover.png", 301, 352),
zIndex: 900,
globalAlpha: 0.1,
closeDialogue: !0,
init: function(c, d, g) {
this.parent(c, d, g);
this.addAnim("idle", 1, [0]);
this.currentAnim = this.anims.idle;
this.tween({
pos: {
x: 89,
y: 120
}
}, 0.5, {
easing: ig.Tween.Easing.Back.EaseInOut
}).start();
this.storage = new ig.Storage;
this.storage.initUnset("highscore-CTF", 0);
this.storage.initUnset("highscore-CTF2", 0);
this.storage.initUnset("highscore-CTF3", 0);
ig.global.score > this.storage.get("highscore-CTF") ? (this.storage.set("highscore-CTF3", this.storage.get("highscore-CTF2")), this.storage.set("highscore-CTF2", this.storage.get("highscore-CTF")), this.storage.set("highscore-CTF", ig.global.score), this.storage.initUnset("highscore-CTF2", 0), this.storage.initUnset("highscore-CTF3", 0)) : ig.global.score > this.storage.get("highscore-CTF2") ?
(this.storage.set("highscore-CTF3", this.storage.get("highscore-CTF2")), this.storage.set("highscore-CTF2", ig.global.score), this.storage.initUnset("highscore-CTF2", 0), this.storage.initUnset("highscore-CTF3", 0)) : ig.global.score > this.storage.get("highscore-CTF3") && this.storage.set("highscore-CTF3", ig.global.score);
this.storage.initUnset("total-CTF", 0);
this.storage.set("total-CTF", this.storage.get("total-CTF") + ig.global.score);
ig.game.spawnEntity(EntityButtonGameover, 23, 700, {
buttonID: 1
});
ig.game.spawnEntity(EntityButtonGameover,
220, 700, {
buttonID: 2
});
ig.game.spawnEntity(EntityButtonGameover, 390, 700, {
buttonID: 3
});
b.set(0.3)
},
update: function() {
this.parent()
},
draw: function() {
this.ctx = ig.system.context;
this.closeDialogue ? (this.ctx.save(), this.ctx.fillStyle = "#000000", this.ctx.globalAlpha = this.globalAlpha, this.ctx.fillRect(0, 0, 480, 640), this.ctx.restore(), this.globalAlpha = 0.7 <= this.globalAlpha ? 0.7 : this.globalAlpha + 0.01) : this.closeDialogue || (this.ctx.save(), this.ctx.fillStyle = "#000000", this.ctx.globalAlpha = this.globalAlpha, this.ctx.fillRect(0,
0, 480, 640), this.ctx.restore(), this.globalAlpha = 0.1 >= this.globalAlpha ? 0 : this.globalAlpha - 0.05);
this.parent();
this.ctx.font = "30px happy-hell";
this.ctx.fillStyle = "#5b2a0b";
this.ctx.textAlign = "center";
this.ctx.fillText(_STRINGS.UI.Best, this.pos.x + 70, this.pos.y + 180);
this.ctx.fillText(_STRINGS.UI.Score, this.pos.x + 70, this.pos.y + 260);
//share
this.ctx.font = "30px happy-hell";
this.ctx.fillStyle = "#ffffff";
this.ctx.textAlign = "left";
this.ctx.fillText(this.storage.getInt("highscore-CTF"), this.pos.x + 140, this.pos.y + 180);
this.ctx.fillText(ig.global.score, this.pos.x + 140, this.pos.y + 260)
},
closeDialogueFunc: function() {
this.closeDialogue && (this.tween({
pos: {
x: 89,
y: -600
}
}, 0.5, {
easing: ig.Tween.Easing.Back.EaseInOut
}).start(), this.closeDialogue = !1)
}
})
});
A simple, versatile way to add menus to canvas graphics is to simply overlay an absolutely positioned DOM structure. Your browser has already all event handling build-in, there is no need to reinvent the wheel.
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d');
ctx.fillStyle = 'rgb(0, 155, 255)';
ctx.fillRect(0, 0, canvas.width, canvas.height)
#container {
position: relative;
display: inline-block;
}
#menu {
position: absolute;
text-align: center;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
#menu a {
padding: 15px;
font-size: 50px;
line-height: 100px;
color: black;
text-shadow: 2px 2px 5px white;
}
#menu a:hover {
color: white;
}
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet" />
<div id='container'>
<canvas id='canvas' width='400' height='100'></canvas>
<div id='menu'>
<a href='http://facebook.com'><i class='fa fa-facebook-official'></i></a>
<a href='http://twitter.com'><i class='fa fa-twitter'></i></a>
<a href='http://whatsapp.com'><i class='fa fa-whatsapp'></i></a>
</div>
</div>
Your browser is capable of rendering such overlay menus very quickly. You should use CSS to style your overlay menu links or buttons.
I have managed to click on a certain element in a canvas.
I have tried to explain with comments what it does.
I have made 3 limits as shown in the image below.
And I'm comparing only with x value, if it's in between these limits. It can be more complex so getCursorPosition() method returns an object with x and y components, just in case if you need to make more comparisons.
https://jsfiddle.net/_jserodio/asa10pye/
var canvas;
var ctx;
// first get your canvas
canvas = document.getElementById('canvas');
canvas.width = 253;
canvas.heigth = 68;
// assign the context
ctx = canvas.getContext("2d");
// asign click event to the canvas
addEventListener("click", listener, false);
function listener(e) {
// if you have 3 buttons
var position = getCursorPosition(e);
var limit1 = canvas.width / 3;
//console.log("limit1: " + limit1);
var limit2 = canvas.width * 2 / 3;
//console.log("limit2: " + limit2);
var limit3 = canvas.width;
//console.log("limit3: " + limit3);
if (position.x < limit1) {
console.log("go to facebook");
//window.open("http://www.facebook.com");
} else if (position.x < limit2) {
console.log("go to twitter");
//window.open("http://www.twitter.com");
} else if (position.x < limit3) {
console.log("go to whatsapp");
//window.open("http://www.whatsapp.com");
}
//console.log("\nx" + position.x);
//console.log("y" + position.y);
}
function getCursorPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
var data = {
x: x,
y: y
};
return data;
}
// load image from data url
var imageObj = new Image();
imageObj.onload = function() {
ctx.drawImage(this, 0, 0);
};
imageObj.src = 'https://justpaste.it/files/justpaste/d307/a11791570/file1.png';
<canvas id='canvas' width="253" height="68">
</canvas>
Bonus!
Here you have a demo I made using this. demo (Draughts/checkers).
You can check the entire code here if you want.
You have several operations in this case.
Option 1:
You'll need to remember the "bounding area" of the three buttons. Anytime the canvas receives a "click", get the click position (How do I get the coordinates of a mouse click on a canvas element?). Once you get the click position. Detect if said click occurs within the bounding area of the button. If it does, use window.open to go there.
Option 2: Similar to Option 1, but instead of remembering a "bounding area", create a separate hidden canvas of the same size as your image with the backgrounds black ('#000000') and the button given distinctive colors (for example, red for Facebook, green for Twitter, and blue for Hangout?).
Then, similar to Option 1, get the click position relative to the canvas. Then use ctx.getImageData(sx, sy, sw, sh) on the context of the hidden canvas layer. If the value you get back is red, then user clicked on Facebook, if green, Twitter, and if blue, Hangout.

Categories