Whilst learning, i keep going back over what i have done to understand it properly, make changes and improvements. At the moment, i am going over my hide/show function.
I have 3 buttons which controls the visibility of their respective canvases.
My code did look like this:
function toggleRedDot(){
document.getElementById('btnRed').click(); {
if(canvas.style.visibility=='hidden'){
canvas.style.visibility='visible';
btnRed.style.color='Red';
}else{
canvas.style.visibility='hidden';
btnRed.style.color='Black';
}
}
};
Both the Green and Blue toggle functions are identical except for the button color. My project actually has 7 buttons and canvases. I'm trying to put all these functions into 1, and this is now what i have:
function toggle_visibility(id)
{
var ele = document.getElementById(id);
if (ele.style.visibility == 'hidden')
{
ele.style.visibility = 'visible';
}
else
{
ele.style.visibility = 'hidden';
}
}
I have been unable to work out how to now change the corresponding button color attribute. The idea is that the button will look 'on' when canvas is visible and the color reverts to Black when 'off'.
I also need the answer to be in just javascript please, as this is what i am learning.
I am also hoping to do a similar sort of thing to my drawDots function as they also differ in class, and location. Thank you.
I made a snippet:
var canvas = document.getElementById('redDot');
var ctxR = canvas.getContext('2d');
var canvas1 = document.getElementById('greenDot');
var ctxG = canvas1.getContext('2d');
var canvas2 = document.getElementById('blueDot');
var ctxB = canvas2.getContext('2d');
var cw = canvas.width;
var ch = canvas.height;
drawDots();
function toggle_visibility(id) {
var ele = document.getElementById(id);
if (ele.style.visibility == 'hidden') {
ele.style.visibility = 'visible';
} else {
ele.style.visibility = 'hidden';
}
}
function drawDots() {
// Red Dot
ctxR.clearRect(0, 0, cw, ch);
ctxR.save();
ctxR.translate(cw / 2, ch / 2);
ctxR.beginPath();
ctxR.arc(-50, 0, 10, 0, 2 * Math.PI);
ctxR.closePath();
ctxR.fillStyle = 'Red';
ctxR.fill();
ctxR.restore();
// Green Dot
ctxG.clearRect(0, 0, cw, ch);
ctxG.save();
ctxG.translate(cw / 2, ch / 2);
ctxG.beginPath();
ctxG.arc(0, 0, 10, 0, 2 * Math.PI);
ctxG.closePath();
ctxG.fillStyle = 'Green';
ctxG.fill();
ctxG.restore();
// Blue Dot
ctxB.clearRect(0, 0, cw, ch);
ctxB.save();
ctxB.translate(cw / 2, ch / 2);
ctxB.beginPath();
ctxB.arc(50, 0, 10, 0, 2 * Math.PI);
ctxB.closePath();
ctxB.fillStyle = 'Blue';
ctxB.fill();
ctxB.restore();
};
#wrapper {
position: relative;
}
canvas {
position: absolute;
border: 1px solid red;
}
.red {
color: Red;
}
.green {
color: Green;
}
.blue {
color: Blue;
}
<div id="btnWrapper">
<button id="btnRed" class="red" title="Toggle Red Dot" onclick="toggle_visibility('redDot')">☉</button>
<button id="btnGreen" class="green" title="Toggle Green Dot" onclick="toggle_visibility('greenDot')">☉</button>
<button id="btnBlue" class="blue" title="Toggle Blue Dot" onclick="toggle_visibility('blueDot')">☉</button>
</div>
<div id="wrapper">
<canvas id="redDot" width="200" height="100"></canvas>
<canvas id="greenDot" width="200" height="100"></canvas>
<canvas id="blueDot" width="200" height="100"></canvas>
</div>
There are a variety of ways to approach this. The way I chose simply passes the id of the button along with the id of the canvas to toggle_visibility. I also added a small object so that the color for a button can be looked up via a button id.
Things to consider in the future:
Because DOM reads/writes are somewhat expensive, consider reading the DOM once and storing the elements on the JS side
Consider using addEventListener from JS instead of onclick in HTML
Your markup will be cleaner
You won't have to pass parameters like btnRed and redDot because event listeners receive an event which knows which element fired the event
Instead of manipulating the visibility/color directly in the JS, have a CSS class (or classes) that do what you want and just change the class.
Good luck! :)
var canvas = document.getElementById('redDot');
var ctxR = canvas.getContext('2d');
var canvas1 = document.getElementById('greenDot');
var ctxG = canvas1.getContext('2d');
var canvas2 = document.getElementById('blueDot');
var ctxB = canvas2.getContext('2d');
var cw = canvas.width;
var ch = canvas.height;
drawDots();
var buttonIdToColor = {
btnRed: 'red',
btnGreen: 'green',
btnBlue: 'blue'
};
function toggle_visibility(buttonId, dotId) {
var button = document.getElementById(buttonId)
var dot = document.getElementById(dotId);
if (dot.style.visibility == 'hidden') {
dot.style.visibility = 'visible';
button.style.color = buttonIdToColor[buttonId];
} else {
dot.style.visibility = 'hidden';
button.style.color = 'black';
}
}
function drawDots() {
// Red Dot
ctxR.clearRect(0, 0, cw, ch);
ctxR.save();
ctxR.translate(cw / 2, ch / 2);
ctxR.beginPath();
ctxR.arc(-50, 0, 10, 0, 2 * Math.PI);
ctxR.closePath();
ctxR.fillStyle = 'Red';
ctxR.fill();
ctxR.restore();
// Green Dot
ctxG.clearRect(0, 0, cw, ch);
ctxG.save();
ctxG.translate(cw / 2, ch / 2);
ctxG.beginPath();
ctxG.arc(0, 0, 10, 0, 2 * Math.PI);
ctxG.closePath();
ctxG.fillStyle = 'Green';
ctxG.fill();
ctxG.restore();
// Blue Dot
ctxB.clearRect(0, 0, cw, ch);
ctxB.save();
ctxB.translate(cw / 2, ch / 2);
ctxB.beginPath();
ctxB.arc(50, 0, 10, 0, 2 * Math.PI);
ctxB.closePath();
ctxB.fillStyle = 'Blue';
ctxB.fill();
ctxB.restore();
};
#wrapper {
position: relative;
}
canvas {
position: absolute;
border: 1px solid red;
}
.red {
color: Red;
}
.green {
color: Green;
}
.blue {
color: Blue;
}
<div id="btnWrapper">
<button id="btnRed" class="red" title="Toggle Red Dot" onclick="toggle_visibility('btnRed', 'redDot')">☉</button>
<button id="btnGreen" class="green" title="Toggle Green Dot" onclick="toggle_visibility('btnGreen', 'greenDot')">☉</button>
<button id="btnBlue" class="blue" title="Toggle Blue Dot" onclick="toggle_visibility('btnBlue', 'blueDot')">☉</button>
</div>
<div id="wrapper">
<canvas id="redDot" width="200" height="100"></canvas>
<canvas id="greenDot" width="200" height="100"></canvas>
<canvas id="blueDot" width="200" height="100"></canvas>
</div>
As you say there are more than 3 colours I have written an example that will simplify the creation of multiply buttons.
The buttons and canvases are uniquely identified by their colour so I use the colours to query the DOM for references and use a function closure (forEach callback function) to hold the references for when needed.
I would have actually created the buttons and canvas inside that function as well but you may want it in the html so there will still be so tedious markup to create when adding more buttons.
Rather than manipulate the element styles I have add the DOM classes, .black for the button, and .hide and .show for the canvas. So all that is needed is to set the element's class name rather than their styles. Will make it easier if you have a complicated set of styles to switch between.
"Red,Green,Blue".split(",").forEach((col,i)=>{
var col_l = col.toLowerCase();
var ctx = document.getElementById(col_l+ 'Dot').getContext('2d');
var btn = document.getElementById("btn"+col);
btn.title ="Toggle "+col +" Dot" ;
btn.className = col_l ;
ctx.canvas.className = "show";
var cw = ctx.canvas.width;
var ch = ctx.canvas.height;
ctx.clearRect(0, 0, cw, ch);
ctx.fillStyle = col;
ctx.beginPath();
ctx.arc(cw/2 -50 + i * 50, ch/2, 10, 0, 2 * Math.PI);
ctx.fill();
function toggle(){ // this function has closure over col_l ,ctx, and btn
if (ctx.canvas.className == 'hide') {
ctx.canvas.className = "show";
btn.className = col_l ;
}else{
ctx.canvas.className = "hide";
btn.className = "black";
}
}
btn.addEventListener("click",toggle);
});
#wrapper {
position: relative;
}
canvas {
position: absolute;
border: 1px solid red;
}
/* for canvas hide and show */
.hide {
visibility : hidden;
}
.show {
visibility : visible;
}
.red {
color: Red;
}
.green {
color: Green;
}
.blue {
color: Blue;
}
.black { /* "black" colour button */
color: Black;
}
<!-- removed most of the stuff that is better added in code. -->
<div id="btnWrapper">
<button id="btnRed">☉</button>
<button id="btnGreen">☉</button>
<button id="btnBlue">☉</button>
</div>
<div id="wrapper">
<canvas id="redDot" width="200" height="100"></canvas>
<canvas id="greenDot" width="200" height="100"></canvas>
<canvas id="blueDot" width="200" height="100"></canvas>
</div>
Related
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}
I found another stack overflow post on this topic where the answer included using: document.getCSSCanvasContext and WebKit to interact with the canvas serving as the CSS background, but that function is depreciated. How else could you make a CSS background a canvas?
Here's the code from the other answer that doesn't work anymore:
<html>
<head>
<style>
div { background: -webkit-canvas(squares); width:600px; height:600px; border:2px solid black }
</style>
<script type="application/x-javascript">
function draw(w, h) {
var ctx = document.getCSSCanvasContext("2d", "squares", w, h);
ctx.fillStyle = "rgb(200,0,0)";
ctx.fillRect (10, 10, 55, 50);
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect (30, 30, 55, 50);
}
</script>
</head>
<body onload="draw(300, 300)">
<div></div>
</body>
</html>
There's a new(ish) API called CSS Houdini that allows you to do exactly what you wanted: https://developer.mozilla.org/en-US/docs/Web/API/PaintWorklet. It's browser support is still super minimal (chromium only currently). There is a pollyfill that should help expand support if needed.
Here's an overview of houdini
There's also an answer with this in the previous stack overflow answers: use canvas as a css background. The links from that answer are incredibly helpful. Especially (two links down) https://twitter.com/DasSurma/status/983305990731894785, which gave an example of how to get the API to work without importing a separate file.
if ("paintWorklet" in CSS) {
const src = document.querySelector('script[language$="paint"]').innerHTML;
const blob = new Blob([src], {
type: 'text/javascript'
});
CSS.paintWorklet.addModule(URL.createObjectURL(blob));
}
.squares {
background-image: paint(squares);
width: 200px;
height: 200px;
border: 2px solid black;
}
.checkboxes {
background-image: paint(checkerboard);
width: 200px;
height: 200px;
border: 2px solid black;
--checkerboard-spacing: 10;
--checkerboard-size: 32;
}
<div style="display:flex;">
<div class="squares">
</div>
<div class="checkboxes">
</div>
</div>
<script language="javascript+paint">
class SquaresPainter {
paint(ctx, gemetry, properties) {
ctx.fillStyle = 'rgb(200,0,0)';
ctx.fillRect(10, 10, 55, 50);
ctx.fillStyle = 'rgba(0, 0, 200, 0.5)';
ctx.fillRect(30, 30, 55, 50);
}
}
registerPaint('squares', SquaresPainter);
// checkerboard.js from https://developers.google.com/web/updates/2018/01/paintapi
class CheckerboardPainter {
// inputProperties returns a list of CSS properties that this paint function gets access to
static get inputProperties() {
return ['--checkerboard-spacing', '--checkerboard-size'];
}
paint(ctx, geom, properties) {
// Paint worklet uses CSS Typed OM to model the input values.
// As of now, they are mostly wrappers around strings,
// but will be augmented to hold more accessible data over time.
const size = parseInt(properties.get('--checkerboard-size').toString());
const spacing = parseInt(
properties.get('--checkerboard-spacing').toString()
);
const colors = ['red', 'green', 'blue'];
for (let y = 0; y < geom.height / size; y++) {
for (let x = 0; x < geom.width / size; x++) {
ctx.fillStyle = colors[(x + y) % colors.length];
ctx.beginPath();
ctx.rect(x * (size + spacing), y * (size + spacing), size, size);
ctx.fill();
}
}
}
}
registerPaint('checkerboard', CheckerboardPainter);
</script>
What is the fastest way to create copy this image without green color or making green color transparent or removing green color from 100X100 px top left
In this case do I have to check every pixel value?
This process is too slow, eg: for 100X100px it takes 40000 loops for checking all rgba values
In browsers that do support it, you could make use of svg filters to do it:
Here is an other Q/A that shows an interesting way of doing this for a fixed color.
Here I made a simple helper function that will set up for us the required tableValues with a bit of tolerance, and I removed the <feFill> so the selected color become transparent (<feFill> would taint the canvas in Chrome).
If you wish to replace the color, you can still achieve it with the canvas' compositing options (commented code in below snippet).
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = e => {
canvas.width = img.width;
canvas.height = img.height;
// update our filter
updateChroma([76, 237, 0], 8);
// if you wish to replace the color, uncomment followings
// ctx.fillStyle = "your_replaceColor";
// ctx.fillRect(0,0,img.width,img.height);
ctx.filter = 'url(#chroma)';
ctx.drawImage(img, 0, 0);
ctx.filter = 'none';
// ctx.globalCompositeOperation = 'destination-in';
// ctx.drawImage(img, 0,0);
};
img.src = "https://i.stack.imgur.com/hZm8o.png";
function updateChroma(rgb, tolerance) {
const sels = ['R', 'G', 'B'];
rgb.forEach((value, ind) => {
const fe = document.querySelector('#chroma feFunc' + sels[ind]);
let vals = '';
if (!value) {
vals = '0'
} else {
for (let i = 0; i < 256; i++) {
vals += (Math.abs(value - i) <= tolerance) ? '1 ' : '0 ';
}
}
fe.setAttribute('tableValues', vals);
});
}
canvas {
background: ivory
}
<svg width="0" height="0" style="position:absolute;visibility:hidden">
<filter id="chroma" color-interpolation-filters="sRGB"x="0" y="0" height="100%" width="100%">
<feComponentTransfer>
<feFuncR type="discrete"/>
<feFuncG type="discrete"/>
<feFuncB type="discrete"/>
</feComponentTransfer>
<feColorMatrix type="matrix" values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
1 1 1 1 -1" result="selected"/>
<feComposite in="SourceGraphic" in2="selected" operator="out"/>
</filter>
</svg>
<canvas id="canvas"></canvas>
I didn't do extensive tests on many devices, but where Hardware Acceleration is enabled, this might perform better than any pixel loop, since it should be all done on GPU.
But browser support is still not that great...
So you may need to fallback to pixel manips anyway.
Here, depending on what it is you are doing the chroma on, you may want to sacrifice a bit of quality for speed.
For instance, on video, you can perform the chroma on a downsized canvas, then draw it back with compositing on the main one, winning a few iterations per frames. See this previous Q/A for an example.
If you check every pixel value and remove the green you'll be left with a ugly image with holes. An easier way to do it and with better results is planning in advance. This is produced with canvas. When you draw the image you may save every circle in an array, and then redraw everything after excluding the green circles.
In the next example click the color to choose it or click the D to remove the green circles.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width = 300,
cx = cw / 2;
var ch = canvas.height = 180,
cy = ch / 2;
var color = "blue";
var drawing = false;
var points= [];
class Point{
constructor(color,x,y){
this.color = color;
this.x = x;
this.y = y
}
draw(){
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x,this.y,5,0,2*Math.PI);
ctx.fill()
}
}
canvas.addEventListener('mousedown', function(evt) {
drawing = true;
}, false);
canvas.addEventListener('mouseup', function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mouseout", function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mousemove", function(evt) {
if (drawing) {
ctx.clearRect(0, 0, cw, ch);
m = oMousePos(canvas, evt);
var point = new Point(color,m.x,m.y);
//point.draw();
points.push(point);
points.forEach((p) =>{
p.draw()
})
}
}, false);
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return {
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
colors.addEventListener("click", (e)=>{
if(e.target.tagName == "SPAN"){color = e.target.id;
}else if(e.target.id == "deleteGreen"){
ctx.clearRect(0,0,cw,ch);
points.forEach( p => {
if(p.color !== "green"){p.draw()}
})
}
})
body {
background-color: #eee;
}
#app {
display: block;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 300px;
height: 300px;
}
canvas {
background: #fff;
border-radius: 3px;
box-shadow: 0px 0px 15px 3px #ccc;
cursor: pointer;
}
#colors {
display: flex;
margin-top: 1em;
justify-content: space-between;
}
#colors span, #deleteGreen {
display: block;
width: 50px;
height: 50px;
border: 1px solid #d9d9d9;
}
#green {
background-color: green;
}
#gold {
background-color: gold;
}
#tomato {
background-color: tomato;
}
#blue {
background-color: blue;
}
#deleteGreen {
text-align: center;
line-height: 50px;
}
<div id="app">
<canvas id="canvas">:( </canvas>
<div id="colors" >
<span id="green"></span>
<span id="gold"></span>
<span id="tomato"></span>
<span id="blue"></span>
<div id="deleteGreen">D</div>
</div>
</div>
I have a problem with the canvas in javscript, I have a function that passes four values to another function as soon as I click an image, each image passes different values, these values I use to draw the chart with rectangles on the canvas, and it works! But as soon as I click another image, the rectangle values of the graph do not change, they remain the same.
Where's the problem?
html
<canvas id="canvas" width="500" height="250"></canvas>
javascript
function gestoreInformazioni(){
for (var i = 0; i < pizzeRegioni.length; i++){
var pizza = pizzeRegioni[i];
if( this.id === pizza.nome ){
generaGrafico(pizza.prezzo, pizza.carboidrati, pizza.grassi, pizza.proteine);
}
}
}
function generaGrafico(prezzo, carboidrati, grassi, proteine){
try{
var ctx = canvas.getContext("2d");
// ctx.beginPath();
ctx.fillStyle='rgb(255,255,0)';
ctx.translate(0,200);
ctx.fillRect(10,-(prezzo),40,150);
ctx.fillRect(100,-(carboidrati),40,150);
ctx.fillRect(200,-(grassi),40,150);
ctx.fillRect(300,-(proteine),40,150);
}catch(e){
alert("generaGrafico " + e);
}
}
init
nodoImgPizza = document.getElementsByClassName("img_pizza");
for(var i = 0; i<nodoImgPizza.length; i++){
nodoImgPizza[i].onclick = gestoreInformazioni;
}
accociativeArray
var pizzeRegioni=[
{
nome:"Lardellata",
regione:"Toscana",
ingredienti :["pomodoro","mozzarella","lardo di Colonnata","ciliegini","porcini"],
minX:134,
minY:128,
maxX:202,
maxY:222,
prezzo:6.50,
carboidrati : 10,
grassi : 15,
proteine : 20,
},
{
nome:"Senese",
regione:"Toscana",
ingredienti :["pomodoro","mozzarella","crudo di cinta senese","porcini"],
minX:134,
minY:128,
maxX:202,
maxY:222,
prezzo:10,
carboidrati : 100,
grassi : 30,
proteine : 40,
}
];
To Re-Draw the same canvas over and over again, first you must reset canvas and then you can draw again.
See this snippet example:
// on document ready
document.addEventListener("DOMContentLoaded", function(){
function grafic(price, carbo, fat, protein){
try{
// Canvas Element
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d");
// Reset Canvas
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, 500, 400);
// Draw (Re-Draw) Canvas
ctx.fillStyle='rgb(255,255,0)';
ctx.translate(0,200);
ctx.fillRect(10,-(price),40,150);
ctx.fillRect(100,-(carbo),40,150);
ctx.fillRect(200,-(fat),40,150);
ctx.fillRect(300,-(protein),40,150);
} catch(e){
alert("generaGrafico " + e);
}
}
// test buttons
document.getElementById("a").addEventListener("click",function() {
grafic(20, 40, 60, 80);
})
document.getElementById("b").addEventListener("click",function() {
grafic(40, 20, 60, 100);
})
document.getElementById("c").addEventListener("click",function() {
grafic(60, 40, 20, 10);
})
document.getElementById("d").addEventListener("click",function() {
grafic(80, 100, 40, 20);
})
});
div {
margin: 5px;
padding: 5px;
display: inline-block;
border: 1px solid black;
cursor: pointer;
}
<div id="a">Image A</div>
<div id="b">Image B</div>
<div id="c">Image C</div>
<div id="d">Image D</div>
<br/>
<canvas id="canvas" width="500" height="250"></canvas>
There is a typo "far" should be "fat"
ctx.fillRect(200,-(far),40,150);
I've slightly edited your code to demonstrate it works now.
function grafic(price, carbo, fat, protein){
try{
var canvas = document.getElementById("canvas")
var ctx = canvas.getContext("2d");
ctx.fillStyle='rgb(255,255,0)';
ctx.translate(0,200);
ctx.fillRect(10,-(price),40,150);
ctx.fillRect(100,-(carbo),40,150);
ctx.fillRect(200,-(fat),40,150);
ctx.fillRect(300,-(protein),40,150);
}catch(e){
alert("generaGrafico " + e);
}
}
grafic(4,4,4,4);
<canvas id="canvas" width="500" height="250" style="border:1px solid; height 20px; min-widht: 200px"></canvas>
Simply put, I'm trying to toggle a button to make a line bold (or not). I read a few questions here similar to this problem, but the solutions haven't helped me. Here's my code:
<!DOCTYPE HTML>
<html>
<head>
<style>
body {
margin: 0px;
padding: 0px;
}
</style>
</head>
<body>
<div id="DrawLineDiv">
<canvas id="DrawLineCanvas" width="578" height="200"></canvas>
<script>
var canvas = document.getElementById('DrawLineCanvas');
var context = canvas.getContext('2d');
// Use beginPath() to declare that a new path is to be drawn
context.beginPath();
// Place the drawing cursor at the desired point
context.moveTo(100, 150);
// Determine where to stop drawing
context.lineTo(450,50);
//Draw the line
context.stroke();
</script>
</div>
<script>
var canvas = document.getElementById("DrawLineCanvas");
//var context = canvas.getContext('2d');
function toggleLineBold(button) {
var button;
if (button == "BoldNow") {
context.lineWidth = 15;
context.stroke();
document.getElementById("BoldLineButton").onclick = function(){
toggleLineBold('Regular');
};
} else {
context.lineWidth = 1;
context.stroke();
document.getElementById("BoldLineButton").onclick = function(){
toggleLineBold('BoldNow');
};
return;
};
};
</script>
<div id="BoldLineButton" style="height:50px; width:120px; border:2px solid #6495ed; background-color:#bcd2ee; border-radius:10px; margin-left: 5px; text-align:center" onclick="toggleLineBold('BoldNow')">
<br/>Toggle Bold Line<br/>
</div>
</body>
</html>
The line changes to bold, but triggers an error in the javascript at the line trying to change the onclick event. I know I've got something wrong, I'm just not sure what.
Thank's in advance for your assistance.
LIVE DEMO
HTML:
<canvas id="DrawLineCanvas" width="578" height="200"></canvas>
<button id="BoldLineButton">Line size: <b>1</b></button>
JS:
var doc = document,
canvas = doc.querySelector('#DrawLineCanvas'),
boldBtn = doc.querySelector('#BoldLineButton'),
ctx = canvas.getContext('2d'),
size = [1, 3, 5, 10, 15], // use only [1, 15] if you want
currSize = 0; // size[0] = 1 // Index pointer to get the value out of the
// size Array
function draw(){
canvas.width = canvas.width;
ctx.beginPath();
ctx.moveTo(100, 150);
ctx.lineTo(450,50);
ctx.lineWidth = size[currSize]; // Use currSize Array index
ctx.stroke();
}
draw();
function toggleLineBold() {
++currSize; // Increase size and
currSize %= size.length; // loop if needed.
boldBtn.getElementsByTagName('b')[0].innerHTML = size[currSize];
draw();
}
boldBtn.addEventListener("click", toggleLineBold);