I have been having a problem with a drawing on a canvas. I want the user to be able to change the background on the canvas and I will eventually want to be able to type something in an input box and it will appear on the canvas in boxes. I have been trying to get more on the screen after they have choosen a background (which works fine), but when I try to add just a simple box I can't. I have been trying to do it in different parts of the code but doesn't work for me, and haven't been able to find a solution.
So what I am asking, is there a specific way you have to do it in order to have
a image (choosen with a select tag, which I already have working), and be able to draw a box ontop of the image choosen. Hope someone can explain to me how I will be able to do so!
function load(){
draw()
}
//Canvas change background
function draw(){
changeBackground("assets/images/1.jpg");
}
function changeBackground(imagePath){
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
var img=new Image();
img.onload = function(){
ctx.drawImage(img,0,0,954,507);
};
img.src=imagePath;
}
function background(){
var imageN = document.getElementById("imageselector").value;
console.log("Image picked as a Background: " + imageN)
changeBackground("assets/images/" + imageN + ".jpg");
}
Let this be a good foundation for your app!
Happy coding!
const canvas = document.querySelector("#canvas");
const context = canvas.getContext("2d");
const imageInput = document.querySelector("#imageInput");
const textInput = document.querySelector("#textInput");
const imageUrl = 'https://i.picsum.photos/id/944/600/600.jpg';
imageInput.value = imageUrl;
const state = {
image: null,
text: ''
}
const width = window.innerWidth;
const height = window.innerHeight;;
canvas.width = width;
canvas.height = height;
textInput.addEventListener("keydown", ({key}) => {
const value = textInput.value;
if(key === "Backspace") {
state.text = value.substring(0, value.length - 1);
}
else {
state.text = value + key;
}
render();
})
imageInput.addEventListener("input", () => {
getImage(imageInput.value)
.then(image => {
state.image = image;
render();
})
})
const render = () => {
clear();
drawBackground(state.image);
drawText(state.text);
}
const drawText = (text) => {
context.font = "40px Comic Sans";
context.fillStyle = "white";
const textMeasure = context.measureText(text);
context.fillText(text, width / 2 - textMeasure.width / 2, height / 5);
}
const clear = () => {
context.fillStyle = "white";
context.fillRect(0, 0, width, height);
}
const getImage = (imagePath) => new Promise((resolve, reject) => {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.src = imagePath;
})
const drawBackground = (image) => {
context.drawImage(image, 0, 0, width, height);
}
getImage(imageInput.value)
.then(image => {
state.image = image;
render();
})
html, body {
margin: 0;
height: 100%;
box-sizing: border-box;
}
#box {
display: flex;
justify-content: center;
align-items: center;
height: 100%;
}
#wrapper {
background: white;
}
#textInput, #imageInput {
width: 300px;
padding: 1rem;
background: transparent;
border: none;
}
#canvas {
position: absolute;
z-Index: -1;
}
<canvas id="canvas"></canvas>
<div id="box">
<div id="wrapper">
<input id="imageInput" type="text" placeholder="Enter src or dataUri of an image...">
<div>
<input id="textInput" type="text" placeholder="Enter text to show up...">
</div>
</div>
</div>
I just had to delay the text and boxes and then it worked :)
Related
I am trying to create a logo and try to add a logo in the middle of the qr code and i am able to generate the qr code but unable to get the qr code with logo on the middle i have tried this code but unable to get the result getting this error
Error: You need to specify a canvas element
and i am using this library https://github.com/soldair/node-qrcode
and here is the tried code
const QRCode = require("qrcode");
const getQRcodeImage = async () => {
try {
let canvas = await QRCode.toCanvas(`my sample text`);
//adding a log at center
const imgDim = { width: 30, height: 30 }; //logo dimention
var context = canvas.getContext("2d");
var imageObj = new Image();
imageObj.src = "./Capture.png";
imageObj.onload = function () {
context.drawImage(
imageObj,
canvas.width / 2 - imgDim.width / 2,
canvas.height / 2 - imgDim.height / 2,
imgDim.width,
imgDim.height
);
};
return canvas;
} catch (e) {
console.error(e);
return "";
}
};
getQRcodeImage();
const QRCode = require("qrcode");
const { createCanvas, loadImage } = require("canvas");
async function create(dataForQRcode, center_image, width, cwidth) {
const canvas = createCanvas(width, width);
QRCode.toCanvas(
canvas,
dataForQRcode,
{
errorCorrectionLevel: "H",
margin: 1,
color: {
dark: "#000000",
light: "#ffffff",
},
}
);
const ctx = canvas.getContext("2d");
const img = await loadImage(center_image);
const center = (width - cwidth) / 2;
ctx.drawImage(img, center, center, cwidth, cwidth);
return canvas.toDataURL("image/png");
}
async function main() {
const qrCode = await create(
"http://shauryamuttreja.com/qr/",
"data:image/png;base64,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",
150,
50
);
console.log(qrCode);
}
main();
Context
I'm trying to generate a Base64 string of a JPG/PNG/... image after resizing it. For some reason, the resulting Base64 string is that of a plain white image.
Assumption
My assumption of what is causing this, is the following: <canvas width="X" height="Y"></canvas> does not seem to contain innerHTML, where I would expect it to contain <img src="https://static.wixstatic.com/media/6068b5_b0d5df4c3d094694bb5d348eac41128d~mv2.jpg" crossorigin="anonymous"> However, I cannot figure out why ctx.drawImage() does not handle this.
Related issues
Several other people have brought up this issue, but unfortunately, the proposed solutions didn't seem to resolve my issue. See:
Rendering a base64 image, currently comes back as blank
How can I convert an image into Base64 string using JavaScript?
How to display Base64 images in HTML?
Minimal Working Example
async function init(){
//getDataUrl
var dataUrl = await getDataUrl("https://static.wixstatic.com/media/6068b5_5888cb03ab9643febc221f3e6788d656~mv2.jpg");
console.log(dataUrl); //returns string, but white image?
//Create new image on body tag with dataurl
addBase64Image(dataUrl);
}
function getImageDimensions(file) {
return new Promise (function (resolved, rejected) {
var i = new Image()
i.onload = function(){
resolved({w: i.width, h: i.height})
};
i.src = file
})
}
async function getDataUrl(img_url) {
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
var dimensions = await getImageDimensions(img_url);
console.log(dimensions);
// Set width and height
canvas.width = dimensions.w; //img_url.width
canvas.height = dimensions.h; //img_url.height
var res = await loadImage(ctx, img_url, dimensions.w, dimensions.h);
console.log(res);
res.setAttribute('crossorigin', 'anonymous');
ctx.drawImage(res, dimensions.w, dimensions.h); //issue: <canvas width="2075" height="3112"></canvas> has no innerHTML e.g. <img src="https://static.wixstatic.com/media/6068b5_b0d5df4c3d094694bb5d348eac41128d~mv2.jpg" crossorigin="anonymous">
console.log(canvas);
console.log(ctx);
dataurl = canvas.toDataURL('image/png');
return dataurl;
}
function loadImage(context, path, dimx, dimy){
return new Promise(function(resolve, reject){
var img=new Image();
img.onload = function() {
resolve.call(null, img);
};
img.src=path;
});
}
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return { width: srcWidth*ratio, height: srcHeight*ratio };
}
async function addBase64Image(img_dataurl){
var dimensions = await getImageDimensions(img_dataurl);
console.log(dimensions);
var res = calculateAspectRatioFit(dimensions.w, dimensions.h, 512, 512);
console.log(res);
var image = document.createElement("img");
var imageParent = document.querySelector('body');
image.id = "user_upload";
image.width = res.width;
image.height = res.height;
image.src = img_dataurl;
imageParent.appendChild(image);
}
body {
margin: 0px;
}
#user_upload {
display: block;
margin-left: auto;
margin-right: auto;
border: 2.5px solid;
}
<body onload="init()">
</body>
To be able to draw an image hosted on a different domain onto a canvas
a) the webserver needs to permit it and send the appropriate headers
b) you need to set the crossOrigin property of the image to "anonymous"
Obviously you already figured this out yourself, as you already have the line
res.setAttribute('crossorigin', 'anonymous');
in your code.
The problem is that it happens too late. It needs to be set before assigning an URL to the src property of the image.
So simply move the above line inside the loadImage() function, just before the call to
img.src=path;
Here's an example:
async function init() {
//getDataUrl
var dataUrl = await getDataUrl("https://static.wixstatic.com/media/6068b5_5888cb03ab9643febc221f3e6788d656~mv2.jpg");
console.log(dataUrl); //returns string, but white image?
//Create new image on body tag with dataurl
addBase64Image(dataUrl);
}
function getImageDimensions(file) {
return new Promise(function(resolved, rejected) {
var i = new Image()
i.onload = function() {
resolved({
w: i.width,
h: i.height
})
};
i.src = file
})
}
async function getDataUrl(img_url) {
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
var dimensions = await getImageDimensions(img_url);
console.log(dimensions);
// Set width and height
canvas.width = dimensions.w; //img_url.width
canvas.height = dimensions.h; //img_url.height
var res = await loadImage(ctx, img_url, dimensions.w, dimensions.h);
console.log("asd ", res);
ctx.drawImage(res, 0, 0); //issue: <canvas width="2075" height="3112"></canvas> has no innerHTML e.g. <img src="https://static.wixstatic.com/media/6068b5_b0d5df4c3d094694bb5d348eac41128d~mv2.jpg" crossorigin="anonymous">
console.log(canvas);
console.log(ctx);
dataurl = canvas.toDataURL('image/png');
return dataurl;
}
function loadImage(context, path, dimx, dimy) {
return new Promise(function(resolve, reject) {
var img = new Image();
img.onload = function() {
resolve.call(null, img);
};
img.setAttribute('crossorigin', 'anonymous');
img.src = path;
});
}
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return {
width: srcWidth * ratio,
height: srcHeight * ratio
};
}
async function addBase64Image(img_dataurl) {
var dimensions = await getImageDimensions(img_dataurl);
console.log(dimensions);
var res = calculateAspectRatioFit(dimensions.w, dimensions.h, 512, 512);
console.log(res);
var image = document.createElement("img");
var imageParent = document.querySelector('body');
image.id = "user_upload";
image.width = res.width;
image.height = res.height;
image.src = img_dataurl;
imageParent.appendChild(image);
}
init();
body {
margin: 0px;
}
#user_upload {
display: block;
margin-left: auto;
margin-right: auto;
border: 2.5px solid;
}
I've got a Vue app working but have hit a snag. I'm able to use drawImg on an image hosted elsewhere, but when saving the file and trying to load locally, it does nothing. No error, it just doesn't draw the image. I'm able to get the same image to load in a normal img tag so it isn't a path issue. Below is the code to load the image, both with examples of a saved image and the URL.
loadImg() {
this.img = new Image();
this.img.onload = () => {
this.ctx.drawImage(this.img, 0, 0);
}
//this.img.src = '../../public/body-outline.jpg'; // does nothing
this.img.src = 'https://www.templatesfront.com/wp-content/uploads/2016/05/human-body-outline-5641.jpg'; // works just fine
}
And for completeness, the entire .vue file:
<template>
<v-app>
<div align="center">
<canvas #mousedown="startPainting" #mouseup="finishedPainting" #mousemove="draw" id="canvas"></canvas>
<v-btn #click="clear">Clear Area</v-btn>
<v-btn #click="saveImg">Save</v-btn>
</div>
</v-app>
</template>
<script>
/* eslint-disable */
export default {
name: 'Canvas',
components: {},
data() {
return {
painting: false,
canvas: null,
ctx: null
}
},
methods: {
startPainting(e) {
this.painting = true;
this.draw(e)
},
finishedPainting() {
this.painting = false;
this.ctx.beginPath()
},
draw(e) {
if(!this.painting) return
this.ctx.lineWidth = 10;
this.ctx.lineCap ="round"
this.ctx.strokeStyle = 'rgba(0,255,255,0.4)';
this.ctx.globalCompositeOperation = "multiply";
if (this.ctx.globalCompositeOperation !== "multiply") // use multiply if available
this.ctx.globalCompositeOperation = 'destination-over'; // fallback mode
this.ctx.lineTo(e.layerX,e.layerY)
this.ctx.stroke()
this.ctx.beginPath()
this.ctx.moveTo(e.layerX,e.layerY)
},
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.loadImg();
},
saveImg() {
let dataUrl = this.canvas.toDataURL();
console.log(dataUrl)
},
loadImg() {
this.img = new Image();
//this.img.crossOrigin = "Anonymous";
this.img.onload = () => {
this.ctx.drawImage(this.img, 0, 0);
}
//this.img.src = '../../public/body-outline.jpg';
this.img.src = 'https://www.templatesfront.com/wp-content/uploads/2016/05/human-body-outline-5641.jpg'
}
},
mounted() {
this.canvas = document.getElementById("canvas");
this.ctx = canvas.getContext("2d");
this.loadImg();
// Resize canvas
this.canvas.height = 400;
this.canvas.width = 800;
}
}
</script>
<style scoped>
#canvas {
border: 1px solid gray;
background-color: white;
}
</style>
This is because you are not using require for the path. You can use the path directly:
this.img.src = '/body-outline.jpg';
I'm facing a problem with css paint worklet and am wondering whether it's a browser bug or there's something that i'm doing wrong.
In a worklet i'm drawing a few rectangles. If one of them covers the whole area, others are starting to disappear when i'm changing zoom level. But when i remove the context.fillRect(0, 0, width, height) everything works like a charm.
Here's sandbox code that i've prepared to better illustrate the problem: https://codesandbox.io/s/magical-villani-py8x2
That indeed looks like a bug in Chrome "experimental" implementation, you might want to let them know on their issue tracker, since that should work.
Now, while you are not doing anything bad per se, note that if instead of the non-standard zoom we do use the transform property, then the bug would not occur:
(()=> {
if( !CSS.paintWorklet ) {
return console.error('CSS Paint API is not supported in this browser, you may have to enable it from chrome://flags/#enable-experimental-web-platform-features');
}
const worklet_script = document.querySelector('[type="paint-worklet"]').textContent;
const worklet_blob = new Blob([worklet_script], { type: 'text/javascript' });
CSS.paintWorklet.addModule(URL.createObjectURL(worklet_blob));
window.addEventListener("DOMContentLoaded", () => {
const slider = document.getElementById("slider");
slider.addEventListener("input", () => {
const el = document.querySelector(".content");
el.style.transform = `scale(${slider.value},${slider.value})`;
});
});
})();
.content {
background: paint(sandbox);
border: 1px solid black;
height: 200px;
width: 200px;
transform-origin: top left;
}
<input type="range" id="slider" min="0.5" max="4" value="1" step="0.1" />
<div class="content"></div>
<script type="paint-worklet">
class SandboxPaintWorklet {
paint(context, geometry, properties) {
const { width, height } = geometry;
// background
context.fillStyle = "#8866aa";
context.fillRect(0, 0, width, height);
context.fillStyle = "#000000";
context.beginPath();
// vertical line
context.fillRect((width * 3) / 4, 0, 1, height);
// horizontal lines
const distance = Math.ceil(height / 20);
for (let i = 0; i < 20; ++i) {
context.fillRect(0, i * distance, width / 2, 1);
}
}
}
registerPaint("sandbox", SandboxPaintWorklet);
</script>
And even with zoom, if instead of that many fillRect, if we do fill a single sub-path by using rect() instead, then that would also work.
(()=> {
if( !CSS.paintWorklet ) {
return console.error('CSS Paint API is not supported in this browser, you may have to enable it from chrome://flags/#enable-experimental-web-platform-features');
}
const worklet_script = document.querySelector('[type="paint-worklet"]').textContent;
const worklet_blob = new Blob([worklet_script], { type: 'text/javascript' });
CSS.paintWorklet.addModule(URL.createObjectURL(worklet_blob));
window.addEventListener("DOMContentLoaded", () => {
const slider = document.getElementById("slider");
slider.addEventListener("input", () => {
const el = document.querySelector(".content");
el.style.zoom = slider.value;
});
});
})();
.content {
background: paint(sandbox);
border: 1px solid black;
height: 200px;
width: 200px;
}
<input type="range" id="slider" min="0.5" max="4" value="1" step="0.1" />
<div class="content"></div>
<script type="paint-worklet">
class SandboxPaintWorklet {
paint(context, geometry, properties) {
const { width, height } = geometry;
// background
context.fillStyle = "#8866aa";
context.fillRect(0, 0, width, height);
context.fillStyle = "#000000";
context.beginPath();
// vertical line
context.rect((width * 3) / 4, 0, 1, height);
// horizontal lines
const distance = Math.ceil(height / 20);
for (let i = 0; i < 20; ++i) {
context.rect(0, i * distance, width / 2, 1);
}
context.fill();
}
}
registerPaint("sandbox", SandboxPaintWorklet);
</script>
So I tried to make it so that whenever an image is clicked it will draw a circle on a canvas. The image and the canvas are suppose to overlay. However, I have a problem when I have
cnvs.style.position = 'absolute'; active all of my canvas' are stacked on each other on the first image. So if I were to click other images the circle would be drawn on the first image but not on the image clicked. However, if I comment out cnvs.style.position = 'absolute'; the canvas is being connected to the bottom of the image instead of being overlaid. I need to make it so that each canvas and image are overlaid so that when one image is clicked a circle will appear. I'm thinking I have a css problem, but I'm not sure how to fix it.
document.body.onload = addElement;
function addElement() {
// image path
const imagePath = ['https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'];
for (const image of imagePath) {
// get the item id of an image
var slice = image.slice(26, 34);
var id = image;
var hdnName = document.getElementById("sendServ");
const img = document.createElement("img");
img.src = image;
img.classList.add("new");
img.id = slice;
const cnvs = document.createElement("canvas");
cnvs.classList.add("suiteiCanvas");
// cnvs.style.position = 'absolute';
cnvs.style.left = img.offsetLeft + "px";
cnvs.style.top = img.offsetTop + "px";
cnvs.style.display = 'none';
var ctx = cnvs.getContext("2d");
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI, false);
ctx.lineWidth = 15;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
var div = document.createElement("div");
var div1 = document.createElement("div");
div.id = id;
div1.id = '1';
div.classList.add("image");
img.onclick = function draw() {
cnvs.style.display = '';
hdnName.value = img.id;
};
cnvs.onclick = function remove() {
cnvs.style.display = 'none';
hdnName.value = null;
};
document.getElementById('suitei-slider').appendChild(div);
document.getElementById(image).appendChild(img);
document.getElementById(image).appendChild(cnvs);
}
}
// slick slider
canvas.suiteiCanvas{
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border:3px solid rgb(20, 11, 11);
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border:3px solid rgb(20, 11, 11);
}
<div class="multiple-items" id="suitei-slider"></div>
<input type="hidden" id="sendServ">
You need to set your canvases in position: absolute inside a container in position: relative so that your canvases are still contained in the container. Since the containers are not in position: absolute, they don't overlay, but their content will, so your canvases will overlay with the images.
Then, you have to center you canvases (I suspect), so I set the canvases dimensions (for now it's hard coded) and fixed the x position of the circle.
I hope it is what you were looking for.
document.body.onload = addElement;
function addElement() {
// image path
const imagePath = ['https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1', 'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'];
for (const image of imagePath) {
// get the item id of an image
var slice = image.slice(26, 34);
var id = image;
var hdnName = document.getElementById("sendServ");
const img = document.createElement("img");
img.src = image;
img.classList.add("new");
img.id = slice;
const cnvs = document.createElement("canvas");
cnvs.classList.add("suiteiCanvas");
// cnvs.style.position = 'absolute';
cnvs.style.left = img.offsetLeft + "px";
cnvs.style.top = img.offsetTop + "px";
cnvs.style.display = 'none';
cnvs.width = 150;
cnvs.height = 150;
var ctx = cnvs.getContext("2d");
ctx.clearRect(0, 0, cnvs.width, cnvs.height);
ctx.beginPath();
ctx.arc(75, 75, 50, 0, 2 * Math.PI, false);
ctx.lineWidth = 15;
ctx.strokeStyle = '#FF0000';
ctx.stroke();
var div = document.createElement("div");
var div1 = document.createElement("div");
div.id = id;
div1.id = '1';
div.classList.add("image");
img.onclick = function draw() {
cnvs.style.display = '';
hdnName.value = img.id;
};
cnvs.onclick = function remove() {
cnvs.style.display = 'none';
hdnName.value = null;
};
document.getElementById('suitei-slider').appendChild(div);
document.getElementById(image).appendChild(img);
document.getElementById(image).appendChild(cnvs);
}
}
// slick slider
.image {
position: relative; /* add this */
user-select: none; /* and this maybe */
}
canvas.suiteiCanvas{
height: auto;
width: auto;
height: 150px;
max-width: 150px;
/*margin-left: 100px;
margin-right: 100px;*/
border:3px solid rgb(20, 11, 11);
position: absolute; /* add this */
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
/*margin-left: 100px;
margin-right: 100px;*/
border:3px solid rgb(20, 11, 11);
}
<div class="multiple-items" id="suitei-slider"></div>
<input type="hidden" id="sendServ">
Just in case you wonder what each line means here it is a more clean code and with comments on many lines... for my understanding you code is a little confuse. You should use more times functions to be more readable, etc.
let index = 0;
const display = "table"; // or "grid" if horizontal, but this migh depend where you place the rest of the code, cause i added the style to the body
const x = 0;
const y = 0;
const images = {
height: 50,
width: 50,
url: [
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fen%2F8%2F84%2FAssociation_of_Gay_and_Lesbian_Psychiatrists_logo.jpg&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fstatic01.nyt.com%2Fnewsgraphics%2F2016%2F07%2F14%2Fpluto-one-year%2Fassets%2Ficon-pluto.png&f=1&nofb=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Ftse4.mm.bing.net%2Fth%3Fid%3DOIP.oFxADNN67dYP-ke5xg7HbQHaHG%26pid%3DApi&f=1',
'https://external-content.duckduckgo.com/iu/?u=https%3A%2F%2Fmedia.glassdoor.com%2Fsqll%2F1065746%2Felevation-church-squarelogo-1453223965790.png&f=1&nofb=1'
]
}
function createHTML() {
console.log('E: Execute & R: Request & I: Informative');
//loop to go true all images
document.body.style.display = display;
for (const image of images.url) {
//each image will correspond to a canvas element
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
//each canvas element will has is own properties (in this case all the same)
canvas.id = 'option' + [index];
canvas.height = images.height;
canvas.width = images.width;
canvas.style.padding = '10px';
//function to get the corresponded image of that particular canvas element
drawImages(canvas);
//add an event listener for when a user click on the particular canvas
canvas.addEventListener("click", optionClick, false);
//all html part was handle we can append it to the body
document.body.appendChild(canvas);
index++;
}
}
function drawImages(canvas) {
//we need to use the getContext canvas function to draw anything inside the canvas element
const ctx = canvas.getContext('2d');
const background = new Image();
//This is needed because if the drawImage is called from a different place that the createHTML function
//index value will not be at 0 and it will for sure with an heigher id that the one expected
//so we are using regex to remove all letters from the canvas.id and get the number to use it later
index = canvas.id.replace(/\D/g, '');
//console.log('E: Drawing image ' + index + ' on canvas ' + canvas.id);
//get the image url using the index to get the corresponded image
background.src = images.url[index];
//no idea why but to place the image, we need to use the onload event
//https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage
background.onload = function() {
ctx.drawImage(background, 0, 0, canvas.width, canvas.height);
}
}
function drawX(canvas) {
const ctx = canvas.getContext('2d');
console.log('E: Placing X on canvas ' + canvas.id);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(images.width, images.height);
ctx.moveTo(images.height, 0);
ctx.lineTo(0, images.width);
ctx.closePath();
ctx.stroke();
}
function clear(canvas) {
console.log('E: clearing canvas ' + canvas.id);
canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height);
drawImages(canvas);
}
function optionClick(e) {
log = true;
const canvas = document.getElementsByTagName('canvas');
for (const option of canvas) {
if (log) console.log('I: User clicked at option ' + e.target.id + ':' + option.id);
log = false;
if (e.target.id === option.id) {
console.log('R: Drawing request at canvas ' + option.id);
drawX(option);
} else {
console.log('R: Clearing request at canvas ' + option.id);
clear(option);
}
}
}
//We start by calling createHTML (that will handle with all HTML elements)
window.onload = createHTML;
canvas.suiteiCanvas {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border: 3px solid rgb(20, 11, 11);
}
#draw-btn {
font-size: 14px;
padding: 2px 16px 3px 16px;
margin-bottom: 8px;
}
img.new {
height: auto;
width: auto;
max-height: 200px;
max-width: 150px;
margin-left: 100px;
margin-right: 100px;
border: 3px solid rgb(20, 11, 11);
}
<body></body>