I want to add a drawing canvas on top of a website that stretches for the whole height of a website.
I specified
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
and it seems to work for the viewport, but when I scroll, I cannot draw bellow it.
I assume I need to update the canvas.height every time I scroll down, but I cannot seem to find a solution.
Here is the code:
window.addEventListener ("load", () => {
const canvas = document.querySelector ("#canvas");
const ctx = canvas.getContext ("2d");
//Resizing
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//Variables
let painting = false;
function startPosition (e){
painting = true;
draw (e);
}
function finishedPosition (){
painting = false;
ctx.beginPath();
}
function draw (e){
if (!painting) return;
ctx.lineWidth = 1;
ctx.lineCap = "round";
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
//Event listeners
canvas.addEventListener ("mousedown", startPosition);
canvas.addEventListener ("mouseup", finishedPosition);
canvas.addEventListener ("mousemove", draw);
});
I would appreciate any help on this!
This appears to be due to the use of window.innerHeight. Try using document.querySelector('body').clientHeight as that should give you the height of the entire body of the page.
Also, use MouseEvent.pageX and .pageY because .clientY will not update when the user scrolls the page.
Related
Trying to achieve an effect of seeking through a video when the page is scrolled. This has been achieved by exporting all frames of the video to JPEG images, pre-loading the images, and rendering them to a canvas. However, this approach uses a lot of bandwidth and takes a long time to load on slow networks.
Trying to achieve the same effect by rendering a video to a canvas does not play as smoothly as the image-based approach.
Here is a working demo with the video-based approach:
https://codesandbox.io/s/infallible-chaum-grvi0r?file=/index.html
Since HTMLMediaElement.fastSeek() doesn't have widespread browser coverage, how can one achieve a realtime playback rate of 30-60 FPS?
Here is the relevant code for the effect (see CSB link above for the full code):
const video = document.querySelector("video");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
(function tick() {
requestAnimationFrame(tick);
const { scrollHeight, clientHeight, scrollTop } = document.body;
const maxScroll = scrollHeight - clientHeight;
const scrollProgress = scrollTop / maxScroll;
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
// This is the line that causes the issue
video.currentTime = video.duration * scrollProgress;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
})();
The problem is due to specifics of drawImage() as noted here, it should be called only after firing of seeked event. The code below solves the problem by using a sentinel value (seeked = false) which will block the execution of drawImage() until the next seeked event:
const video = document.querySelector("video");
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let seeked;
(function tick() {
requestAnimationFrame(tick);
if (seeked) {
seeked = false;
const { scrollHeight, clientHeight, scrollTop } = document.body;
const maxScroll = scrollHeight - clientHeight;
const scrollProgress = scrollTop / Math.max(maxScroll, 1);
canvas.width = document.body.clientWidth;
canvas.height = document.body.clientHeight;
// This is the line that causes the issue
video.currentTime = video.duration * scrollProgress;
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
}
})();
video.addEventListener('seeked', () => {
seeked = true;
});
video.currentTime = .001;
i have some canvases in my code. All of these are like a blackboard, this canvases let me draw on an image, but when i try to draw in a device nothing happens. This is all of my code:
let configureCanvas = canvas => {
let ctx = canvas.getContext("2d");
let painting = canvas.parentNode;
let paintStyle = getComputedStyle(painting);
if(canvas == document.getElementById("pizarra-musculos")) {
var x = window.matchMedia("(max-width: 700px)")
myFunction(x) // Call listener function at run time
x.addListener(myFunction) // Attach listener function on state changes
function myFunction(x) {
if (x.matches) { // If media query matches
canvas.width = "350";
canvas.height = "350";
} else {
canvas.width = "500";
canvas.height = "500";
}
}
}else{
canvas.width = "350";
canvas.height = "350";
}
let mouse = {
x: 0,
y: 0
};
canvas.ontouchstart = function(e){
var touches = e.touches || [];
var touch = touches[0] || {};
}
canvas.addEventListener('mousemove', function(e) {
mouse.x = e.pageX - this.offsetLeft;
mouse.y = e.pageY - this.offsetTop
}, false);
if(canvas == document.getElementById("pizarra-musculos")) {
//Rojo Claro
ctx.lineWidth = 12;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = 'rgba(255, 8, 53, 0.02)';
document.getElementById("btnRojoClaro").addEventListener("click", ()=>{
//Rojo Claro
ctx.lineWidth = 12;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = 'rgba(255, 8, 53, 0.02)';
});
document.getElementById("btnVerde").addEventListener("click", ()=>{
//Rojo Claro
ctx.lineWidth = 4;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#249120';
});
document.getElementById("btnRojo").addEventListener("click", ()=>{
//Rojo Claro
ctx.lineWidth = 4;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#ff0000';
});
document.getElementById("btnNegro").addEventListener("click", ()=>{
//Negro
ctx.lineWidth = 4;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#000000';
});
document.getElementById("btnAzul").addEventListener("click", ()=>{
//Azul
ctx.lineWidth = 4;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#1e5085';
});
}else{
ctx.lineWidth = 3;
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.strokeStyle = '#e34f54';
};
canvas.addEventListener('mousedown', function(e) {
ctx.beginPath();
ctx.moveTo(mouse.x, mouse.y);
canvas.addEventListener('mousemove', onPaint, false);
}, false);
canvas.addEventListener('mouseup', function() {
canvas.removeEventListener('mousemove', onPaint, false)
}, false);
var onPaint = function() {
ctx.lineTo(mouse.x, mouse.y);
ctx.stroke();
};
canvas.nextElementSibling.addEventListener('click', function() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
}
PD: i learned about the touchscreens on mozila developers but i can't do functionally this canvas on device... Thanks for learning my question.
In javascript, you can add listenner! That means that something will be listenned and a code will be executed when the listenner will be activated !
For instance, if you declare in html <button id="mybutton"></button> in js, you can retrieve this button with document.getElementById("mybutton"), thus, you can manipulate the element itself, value, return, action, etc...,
Let's store this element var button = document.getElementById("mybutton")
now console.log(button) will sort of display this element, cool right ?
On this button you can add listenner, onClick for example that will execute a function when the button is clicked
button.addEvenListenner("onClick", my_function)
So now when you will click on your button you will execute my_function (you need to create my_function of course)
But in your case your want to get the eventListenner of a mouse mouve or when someone use finger on their phone!
You already made it, with 'mousemove' for example ! or 'mouseup' etc...
The thing desktop is a phone and vice versa!
You don't have a mouse on your phone you have your finger, so need to have a listenner when someone touch de screen!
That's where 'touchstart' event listenner is here for, when you touch the screen, you can call a function to do whatever you want with this !
Those four listenner will be enough for your project, why you may ask ?
'touchstart' tells when you touch the screen
'touchend' tells when you remove your finger from the screen
'touchmove' tells when you drag your finger on the screen (touchstart and touchmove are not the same thing !)
'touchcancel' tells when you cancel (Honestly I don't know, you don't need it I guess)
now you need to add your listenner to your canvas
var canvas = document.getElementById("pizarra-musculos")
then you add your listenner
canvas.addEventListener("touchstart", handleStart);
canvas.addEventListener("touchend", handleEnd);
canvas.addEventListener("touchmove", handleMove);
Now when one of those 3 listenner will be used, the function next to them will be called !
Thus, you can execute whatever you want in those function
function handleMove(e) {
// Cache the client X/Y coordinates
var clientX = e.touches[0].clientX;
var clientY = e.touches[0].clientY;
}
With this bit of code you are able to keep track of the x and y of your finger of your screen, try every listenner to know when they are called etc.. put some console.log to print out value, you will do it it's not hard hope it helped you
This question already has answers here:
Drawing semi-transparent lines on mouse movement in HTML5 canvas
(3 answers)
Closed 3 years ago.
Ok so I am working on a drawing application and I would like to make it so the user can change the opacity of their brush. I have gotten the opacity to change by changing the alpha value, but when I draw a line with the lowered alpha value, the line has many dots of different transparency in it. How can I make the semi transparent line draw very clean with occasional transparency changes only at overlaps?
Image 1 is what happens when I run my code
Image 2 is what I would like to achieve if possible
Here is my javascript code for my canvas
const canvas = document.querySelector("#canvas");
const ctx = canvas.getContext('2d');
window.addEventListener('load', function() {
//Resizing
canvas.height = window.innerHeight;
canvas.width = window.innerWidth;
//Variables
let painting = false;
function startPosition(e) {
painting = true;
draw(e);
}
function finishedPosition() {
painting = false;
ctx.beginPath();
}
function draw(e) {
if(!painting) return;
ctx.lineWidth = 10;
ctx.lineCap = "round";
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(e.clientX, e.clientY);
}
//EventListeners
canvas.addEventListener('mousedown', startPosition);
canvas.addEventListener('mouseup', finishedPosition);
canvas.addEventListener('mousemove', draw);
});
I would recommend refractoring your draw() and doing some OOP with canvas (shameless plug of my canvas credibility).
I say OOP so that you can track the users previous coordinates and current coordinates in a much cleaner state.
Once some OOP is put in place, you can take advantage of the following code:
function draw(e) {
//Update Current coords
this.currX = e.clientX
this.currY = e.clientY
ctx.beginPath();
ctx.moveTo(this.prevX, this.prevY);
ctx.lineTo(this.currX, this.currY);
ctx.lineWidth = this.lineWidth; //10
ctx.lineCap = this.lineCap; //round
ctx.strokeStyle = this.lineColor; //blue
ctx.stroke();
ctx.closePath();
// Update coords
this.prevX = this.currX
this.prevY = this.currY
}
I want to create something like scratch card.
I created a canvas and added text to it.I than added a box over the text to hide it.Finally write down the code to erase(scratch) that box.
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
ctx.font = "30px Arial";
ctx.fillText("Hello World",10,50);
ctx.globalCompositeOperation = 'source-over';
ctx.fillStyle='red';
ctx.fillRect(0,0,500,500);
function myFunction(event) {
var x = event.touches[0].clientX;
var y = event.touches[0].clientY;
document.getElementById("demo").innerHTML = x + ", " + y;
ctx.globalCompositeOperation = 'destination-out';
ctx.arc(x,y,30,0,2*Math.PI);
ctx.fill();
}
But the problem is it delete the text also.
How could I only delete that box not the text?
Canvas context keeps only one drawing state, which is the one rendered. If you modify a pixel, it won't remember how it was before, and since it has no built-in concept of layers, when you clear a pixel, it's just a transparent pixel.
So to achieve what you want, the easiest is to build this layering logic yourself, e.g by creating two "off-screen" canvases, as in "not appended in the DOM", one for the scratchable area, and one for the background that should be revealed.
Then on a third canvas, you'll draw both canvases every time. It is this third canvas that will be presented to your user:
var canvas = document.getElementById("myCanvas");
// the context that will be presented to the user
var main = canvas.getContext("2d");
// an offscreen one that will hold the background
var background = canvas.cloneNode().getContext("2d");
// and the one we will scratch
var scratch = canvas.cloneNode().getContext("2d");
generateBackground();
generateScratch();
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
main.clearRect(0,0,canvas.width,canvas.height);
main.drawImage(background.canvas, 0,0);
main.drawImage(scratch.canvas, 0,0);
}
function generateBackground(){
background.font = "30px Arial";
background.fillText("Hello World",10,50);
}
function generateScratch() {
scratch.fillStyle='red';
scratch.fillRect(0,0,500,500);
scratch.globalCompositeOperation = 'destination-out';
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
scratch.beginPath();
scratch.arc(x, y, 30, 0, 2*Math.PI);
scratch.fill();
drawAll();
}
<canvas id="myCanvas"></canvas>
Now, it could all have been done on the same canvas, but performance wise, it's probably not the best, since it implies generating an overly complex sub-path that should get re-rendered at every draw, also, it is not much easier to implement:
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext('2d');
ctx.font = '30px Arial';
drawAll();
// the events handlers
var down = false;
canvas.onmousemove = handlemousemove;
canvas.onmousedown = handlemousedown;
canvas.onmouseup = handlemouseup;
function drawAll() {
ctx.globalCompositeOperation = 'source-over';
// first draw the scratch pad, intact
ctx.fillStyle = 'red';
ctx.fillRect(0,0,500,500);
// then erase with the currently being defined path
// see 'handlemousemove's note
ctx.globalCompositeOperation = 'destination-out';
ctx.fill();
// finally draw the text behind
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'black';
ctx.fillText("Hello World",10,50);
}
function handlemousedown(evt) {
down = true;
handlemousemove(evt);
}
function handlemouseup(evt) {
down = false;
}
function handlemousemove(evt) {
if(!down) return;
var x = evt.clientX - canvas.offsetLeft;
var y = evt.clientY - canvas.offsetTop;
// note how here we don't create a new Path,
// meaning that all the arcs are being added to the single one being rendered
ctx.moveTo(x, y);
ctx.arc(x, y, 30, 0, 2*Math.PI);
drawAll();
}
<canvas id="myCanvas"></canvas>
How could I only delete that box not the text?
You can't, you'll have to redraw the text. Once you've drawn the box over the text, you've obliterated it, it doesn't exist anymore. Canvas is pixel-based, not shape-based like SVG.
What is wrong with my code.I am trying to load the image background on a canvas and then draw few rectangles on the image canvas.my image is not showing up on the canvas or either is it being completely overwritten my rectangles.
I have followed this SO question, but still, it happens.
//Initialize a new Box, add it, and invalidate the canvas
function addRect(x, y, w, h, fill) {
var rect = new Box;
rect.x = x;
rect.y = y;
rect.w = w
rect.h = h;
rect.fill = fill;
boxes.push(rect);
invalidate();
}
// holds all our rectangles
var boxes = [];
// initialize our canvas, add a ghost canvas, set draw loop
// then add everything we want to intially exist on the canvas
function drawbackground(canvas,ctx,onload){
var img = new Image();
img.onload = function(){
// canvas.width = img.width;
// canvas.height = img.height;
ctx.drawImage(img);
//addRect(200, 200, 200, 200, '#FFC02B');
onload(canvas,ctx);
};
img.src = "https://cdnimages.opentip.com/full/VLL/VLL-LET-G.jpg";
}
function init() {
// canvas = fill_canvas();
canvas = document.getElementById('canvas');
HEIGHT = canvas.height;
WIDTH = canvas.width;
ctx = canvas.getContext('2d');
ghostcanvas = document.createElement('canvas');
ghostcanvas.height = HEIGHT;
ghostcanvas.width = WIDTH;
gctx = ghostcanvas.getContext('2d');
// make draw() fire every INTERVAL milliseconds
setInterval(draw, INTERVAL);
// set our events. Up and down are for dragging,
// double click is for making new boxes
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
canvas.ondblclick = myDblClick;
// add custom initialization here:
drawbackground(canvas,ctx);
//context.globalCompositeOperation = 'destination-atop';
// add an orange rectangle
addRect(200, 200, 200, 200, '#FFC02B');
// add a smaller blue rectangle
addRect(25, 90, 250, 150 , '#2BB8FF');
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
...
As the Image loads always asynchronously, and you draw your rects synchronously after drawbackground() call, you will get a canvas with only image on it.
You need to create function which will pe passed as third argument to drawbackground, and call addRect in this function instead of drawbackground
PS:
Your code should throw an exception because
onload(canvas,ctx);
will try to call undefined as a function