Related
Why centering and even adding a header tag is causing the code's buttons to go off of the input area? I want to add some designs to the background but have no idea why the buttons are off of the input area. It is for a canvas game called bug smasher that needs mouse input.
Javascript:
// Create the canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext('2d');
var timer = 0;
var caught = false;
var fps = 10;
document.body.appendChild(canvas);
canvas.width = 800;
canvas.height = 544;
// Background image
var bgReady = false;
var bgImage = new Image();
bgImage.onload = function () {
bgReady = true;
};
bgImage.src = "images/background.png";
// bug image
var bugReady = false;
var bugImage = new Image();
bugImage.onload = function () {
bugReady = true;
};
bugImage.src = "images/bug.png";
var bug = {};
var bugCaught = 0;
// When bug is caught, reset
var reset = function () {
bug.x = 40 + (Math.random() * (canvas.width - 70));
do {
bug.y = 40 + (Math.random() * (canvas.height - 70));
}
while (bug.y < 100)
};
//mousedown event
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(e) {
if (e.button != 0) return;
mouseXinCanvas = e.clientX;
mouseYinCanvas = e.clientY;
if (bugBody(bug, mouseXinCanvas, mouseYinCanvas)) {
caught = true;
clearInterval(timer);
timer = setInterval(reset, 20000 / fps);
reset();
}
if (ResetScore(mouseXinCanvas, mouseYinCanvas)) {
location.reload();
}
if (ResetSpeed(mouseXinCanvas, mouseYinCanvas)) {
clearInterval(timer);
timer = setInterval(reset, 20000 / fps);
reset();
render();
}
};
//bug's body define
function bugBody(bug, x, y) {
if (x <= (bug.x + 80)
&& bug.x <= (x + 80)
&& y <= (bug.y + 80)
&& bug.y <= (y + 80)
) {
fps = fps + 5;
bugCaught++;
return true;
}
return false;
};
//Reset Score box
function ResetScore(x, y) {
if (x > (305)
&& x < (545)
&& y > (15)
&& y < (85)
) {
return true;
}
return false;
};
//Reset speed box
function ResetSpeed(x, y) {
if (x > (605)
&& x < (845)
&& y > (15)
&& y < (85)
) {
fps = 10;
return true;
}
return false;
};
// Draw everything
var render = function () {
//===========================================================
// add the following line to clear the display.
ctx.clearRect(0,0,ctx.canvas.width,ctx.canvas.height);
if (bgReady) {
ctx.drawImage(bgImage, 0, 100);
}
if (bugReady) {
ctx.drawImage(bugImage, bug.x, bug.y);
}
if (caught == true) {
if (bgReady) {
ctx.drawImage(bgImage, 0, 100);
}
caught = false;
}
// Score, Title
ctx.fillStyle = "rgb(65, 226, 24)";
ctx.font = "34px Helvetica";
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.fillText("Bug Smasher", 5, 40);
ctx.font = "20px Helvetica";
ctx.fillText("Score: " + bugCaught, 10, 10);
// Reset Score, Speed button
ctx.fillStyle = "rgb(30, 168, 99)";
ctx.fillRect(250, 10, 250, 80);
ctx.fillRect(520, 10, 250, 80);
ctx.fillStyle = "rgb(30, 168, 99)";
ctx.fillRect(255, 15, 240, 70);
ctx.fillRect(525, 15, 240, 70);
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.font = "34px Arial";
ctx.fillText("Reset Score", 275, 30);
ctx.fillText("Reset Speed", 545, 30);
};
// The main game loop
var main = function () {
render();
// Request to do this again ASAP
requestAnimationFrame(main);
};
// Cross-browser support for requestAnimationFrame
var w = window;
requestAnimationFrame = w.requestAnimationFrame || w.webkitRequestAnimationFrame || w.msRequestAnimationFrame || w.mozRequestAnimationFrame;
// Let's play this game!
//var then = Date.now();
reset();
main();
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content = "width=device-width, initial-scale = 1.0">
<link rel="stylesheet" href="Assignment5.css">
<title>Assignment 5</title>
</head>
<body>
<main>
<script src="Assignment5.js"></script>
</main>
</body>
</html>
When you add other elements to your code your <canvas> element will shift accordingly. What you are not accounting for in your mouse function is the canvas position in relation to the window.
I'm sure you've noticed that when you add elements the canvas moves down but the collision for the button and the bug do not move in relation to the canvas. So in your mouse function you need to subtract the canvas x and y position
window.addEventListener("mousedown", onMouseDown, false);
function onMouseDown(e) {
let canvasBounds = canvas.getBoundingClientRect(); //add this
if (e.button != 0) return;
mouseXinCanvas = e.clientX - canvasBounds.x; //can also be canvasBounds.left
mouseYinCanvas = e.clientY - canvasBounds.y; //can also be canvasBoudns.top
if (bugBody(bug, mouseXinCanvas, mouseYinCanvas)) {...
I am new in HTML5 canvas. I am trying to build game area using HTML canvas where someone can drive around a car inside
Canvas area like if I press the left arrow key the car will turn to the left and if press the right arrow key the car will turn to the right side. And up and down arrow keys are responsible for moving the car upward or downward. And the car must stay within the canvas area. Here, I use rotate method to turn the car right or left. But it is not working, actually the car is getting away from the canvas and behaving like a crap.
Anyone has any idea to solve the problem? I am totally stuck here. Thanks in advance.
Initially I am displaying a rectangle instead of car.
My js file is given below.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = innerWidth - 100;
canvas.height = innerHeight - 100;
const canvasW = canvas.width;
const canvasH = canvas.height;
let upPressed = false,
downPressed = false,
rightPressed = false,
leftPressed = false;
class PlayerCar {
constructor(carX, carY, carWidth, carHeight) {
this.carX = carX;
this.carY = carY;
this.carWidth = carWidth;
this.carHeight = carHeight;
}
draw() {
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.rect(this.carX, this.carY, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
}
}
const playerCar = new PlayerCar(100, 100, 40, 60);
playerCar.draw();
function navigation() {
const handleKeyDown = (e) => {
if (e.key === "ArrowUp") {
upPressed = true;
}
if (e.key === "ArrowDown") {
downPressed = true;
}
if (e.key === "ArrowRight") {
rightPressed = true;
}
if (e.key === "ArrowLeft") {
leftPressed = true;
}
};
const handleKeyUp = (e) => {
if (e.key === "ArrowUp") {
upPressed = false;
}
if (e.key === "ArrowDown") {
downPressed = false;
}
if (e.key === "ArrowRight") {
rightPressed = false;
}
if (e.key === "ArrowLeft") {
leftPressed = false;
}
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvasW, canvasH);
if (upPressed) {
playerCar.carY -= 5;
}
if (downPressed) {
playerCar.carY += 5;
}
if (leftPressed) {
ctx.save();
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate((Math.PI / 180) * 0.2);
playerCar.carX -= 5;
ctx.restore();
}
if (rightPressed) {
ctx.save();
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate((Math.PI / 180) * -0.2);
playerCar.carX += 5;
ctx.restore();
}
if (playerCar.carX < 0) playerCar.carX = 0;
if (playerCar.carX > canvasW - playerCar.carWidth)
playerCar.carX = canvasW - playerCar.carWidth;
if (playerCar.carY < 0) playerCar.carY = 0;
if (playerCar.carY > canvasH - playerCar.carHeight)
playerCar.carY = canvasH - playerCar.carHeight;
playerCar.draw();
}
function startGame() {
animate();
}
startGame();
navigation();
I would first move all of the update info to a method in the car class vice doing it in the animate loop.
This is where cos and sin come in and getting familiar with angles. You also must understand the canvas relation the the object it draws is always at (x, y) of (0, 0) unless you translate it to the center. To do that draw your object in this manner:
ctx.fillStyle = "blue";
ctx.save();
ctx.beginPath();
ctx.translate(this.carX, this.carY)
ctx.rotate(this.angle)
ctx.rect(-this.carWidth/2, -this.carHeight/2, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
ctx.restore();
Use of save() and restore() are a must unless you want to translate and rotate you object back to its original. It's the same ting but simpler. So now I am translating the car around the canvas and the car itself is drawn at negative half the width and height to ensure the canvas (0, 0) corner is in the center of the car. This is because canvas always rotates from the top-left corner.
Now create a method called update() and put you control logic in there:
update() {
if (rightPressed) {
this.angle += this.rot
} else if (leftPressed) {
this.angle -= this.rot
}
if (upPressed) {
this.carX += Math.cos(this.angle+toRadians(-90)) * 5;
this.carY += Math.sin(this.angle+toRadians(-90)) * 5;
}
}
Be aware I added angle and rot to the constructor also. What this is doing is when you press left or right the car rotate accordingly. As for pressing up we are going to translate it by the rotation. Since this would normally make the car drive to the right we have to also add -90 degrees to the current angle to ensure the car drives forwards. Just remove +toRadians(-90) and see what happens.
Multiplying it by 5 is an arbitrary number for speed. You can even make it part of the constructor and set it there. i.e. this.speed = 5
Doing the same thing for downPressed but instead use +toRadians(90)
toRadians() is just a simple function added to your code to convert degrees to radians.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 600;
canvas.height = 600;
const canvasW = canvas.width;
const canvasH = canvas.height;
let upPressed = false,
downPressed = false,
rightPressed = false,
leftPressed = false;
class PlayerCar {
constructor(carX, carY, carWidth, carHeight) {
this.carX = carX;
this.carY = carY;
this.carWidth = carWidth;
this.carHeight = carHeight;
this.angle = 0;
this.rot = 0.1; //control how fast it turns
}
draw() {
ctx.fillStyle = "blue";
ctx.save();
ctx.beginPath();
ctx.translate(this.carX, this.carY)
ctx.rotate(this.angle)
ctx.rect(-this.carWidth/2, -this.carHeight/2, this.carWidth, this.carHeight);
ctx.fill();
ctx.closePath();
ctx.restore();
}
update() {
if (rightPressed) {
this.angle += this.rot
} else if (leftPressed) {
this.angle -= this.rot
}
if (upPressed) {
this.carX += Math.cos(this.angle+toRadians(-90)) * 5;
this.carY += Math.sin(this.angle+toRadians(-90)) * 5;
}
if (downPressed) {
this.carX += Math.cos(this.angle+toRadians(90)) * 5;
this.carY += Math.sin(this.angle+toRadians(90)) * 5;
}
}
}
function toRadians(deg) {
return (deg * Math.PI) / 180;
}
const playerCar = new PlayerCar(100, 100, 40, 60);
playerCar.draw();
function navigation() {
const handleKeyDown = (e) => {
if (e.key === "ArrowUp") {
upPressed = true;
}
if (e.key === "ArrowDown") {
downPressed = true;
}
if (e.key === "ArrowRight") {
rightPressed = true;
}
if (e.key === "ArrowLeft") {
leftPressed = true;
}
};
const handleKeyUp = (e) => {
if (e.key === "ArrowUp") {
upPressed = false;
}
if (e.key === "ArrowDown") {
downPressed = false;
}
if (e.key === "ArrowRight") {
rightPressed = false;
}
if (e.key === "ArrowLeft") {
leftPressed = false;
}
};
document.addEventListener("keydown", handleKeyDown);
document.addEventListener("keyup", handleKeyUp);
}
function animate() {
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvasW, canvasH);
if (playerCar.carX < 0) playerCar.carX = 0;
if (playerCar.carX > canvasW - playerCar.carWidth)
playerCar.carX = canvasW - playerCar.carWidth;
if (playerCar.carY < 0) playerCar.carY = 0;
if (playerCar.carY > canvasH - playerCar.carHeight)
playerCar.carY = canvasH - playerCar.carHeight;
playerCar.draw();
playerCar.update();
}
function startGame() {
animate();
}
startGame();
navigation();
<canvas></canvas>
To be clear on why your code is not doing what you expect think about this. You are trying to translate and rotate the context without ever accessing the object. If you were to add ctx.fillRect() to your animate loop would you think it's going to just know that you want to change the car? Same goes for translate and rotate. Try adding a fillStyle and fillRect to your rightPressed
if (rightPressed) {
ctx.save();
playerCar.carX += 5;
ctx.translate(
playerCar.carX + playerCar.width / 2,
playerCar.carY + playerCar.height / 2
);
ctx.rotate(45);
ctx.fillStyle = 'grey'
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.restore();
}
You will see when you press right the context does what you want just not the context of the object. This is why adding it directly top the class should be done. Now you are specifically targeting the object you want.
I'm trying to make it so that it's just 1 curved line but for some reason it's creating it behind the jagged one I don't want.
I tried adding & removing and for some reason the line still draws when I deleted "beginPath()." But with the bezierCurveTo, it remains jagged but forms a smooth line again when I add "beginPath()" again.
const canvas = document.querySelector('#draw');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.strokeStyle = 'hue(100%, 75%, 50%)';
ctx.lineJoin = 'round';
ctx.lineCap = 'round';
ctx.lineWidth = 5;
ctx.globalCompositeOperation = 'multiply';
let isDrawing = false;
let lastX = 0;
let lastY = 0;
let points = [];
function draw(e) {
if (!isDrawing) return;
var px2 = points.map(point => point.x)[2];
var py2 = points.map(point => point.y)[2];
var px1 = points.map(point => point.x)[1];
var py1 = points.map(point => point.y)[1];
var px0 = points.map(point => point.x)[0];
var py0 = points.map(point => point.y)[0];
ctx.stroke();
ctx.beginPath();
ctx.moveTo(px2, py2);
if (points.length > 3) {
ctx.bezierCurveTo(px1, py1, px0, py0, e.offsetX, e.offsetY);
}
var slope = ((lastY - e.offsetY) + 1) / (Math.abs(lastX - e.offsetX) + 1);
if (slope < -0.5) {
if (ctx.lineWidth < 15) {
ctx.lineWidth++;
}
} else {
if (ctx.lineWidth > 3) {
ctx.lineWidth -= 1;
}
}
[lastX, lastY] = [e.offsetX, e.offsetY];
points.unshift({
x: lastX,
y: lastY,
size: ctx.lineWidth,
color: ctx.strokeStyle,
mode: "draw"
});
}
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
ctx.lineWidth = 5;
points = [];
});
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseout', () => isDrawing = false);
<canvas id="draw"></canvas>
I'm expecting the end result to just have 1 curved line and remove the jagged one layered on top.
You'll also only be able to see the 2 layers when you draw a curve really fast.
Any help is appreciated.
I have a canvas function which draws a square if I click on the canvas field and move the mouse, that works so far.
My Problem is that if I release the mouse and click at the canvas again the old drawn rectangle vanishes.
How do I make it possible that the old drawn does not get vanished.
My function:
function foo() {
var tool = this;
this.started = false;
var canvasx = canvas.offsetLeft;
var canvasy = canvas.offsetTop;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = parseInt(ev.clientX-canvasx);
last_mousey = parseInt(ev.clientY-canvasy);
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = parseInt(ev.clientX-canvasx);
mousey = parseInt(ev.clientY-canvasy);
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
tool.started = false;
}
};
}
It Looks something like this: http://jsfiddle.net/AbdiasSoftware/kqW4X/
The old drawn rectangle vanishes on click because, you are clearing the entire canvas each time before drawing a rectangle.
The easiest workaround would be to save the entire canvas as an image on mouseup and draw that image before drawing each rectangle.
var canvas;
var _foo = new foo();
canvas.onmousedown = _foo.mousedown;
canvas.onmousemove= _foo.mousemove;
canvas.onmouseup = _foo.mouseup;
function foo() {
canvas = $('#canvas')[0];
var context = canvas.getContext('2d');
var checkboxSquare = $('#checkboxSquare')[0];
var img = new Image();
var tool = this;
this.started = false;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
this.mousedown = function (ev) {
if(checkboxSquare.checked) {
last_mousex = ev.offsetX;
last_mousey = ev.offsetY;
context.strokeStyle = $('#selectColor').val();
context.lineWidth = $('#selectWidth').val();
tool.started = true;
}
};
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
mousex = ev.offsetX;
mousey = ev.offsetY;
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
context.drawImage(img, 0, 0); // draw saved canvas (image)
context.beginPath();
var width = mousex-last_mousex;
var height = mousey-last_mousey;
context.rect(last_mousex,last_mousey,width,height);
context.stroke();
}
};
this.mouseup = function (ev) {
if (tool.started && checkboxSquare.checked) {
tool.mousemove(ev);
img.src = canvas.toDataURL(); // save canvas as image
tool.started = false;
}
};
}
canvas {
border: 1px solid black;
cursor: default;
margin-top: 5px
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input type="checkbox" id="checkboxSquare">Square | Color
<select id="selectColor">
<option value="red">red</option>
<option value="green">green</option>
<option value="blue">blue</option>
</select> | Width
<select id="selectWidth">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<canvas id="canvas" width="400" height="400"></canvas>
Just create a background canvas same as the main canvas. When you drag out a new box, first draw the background canvas (with all the past boxes) on the main canvas then the current box being drawn. When you finish dragging the box, just daw it to the background canvas.
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
const boxStyle = {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
}
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
function createBox(x,y,w,h,style){ return {x,y,w,h,style,draw : drawBox} }
function drawBox(ctx){
setStyle(ctx, this.style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
ctx.fill();
ctx.stroke();
}
function setStyle(ctx, style){ Object.keys(style).forEach(key => ctx[key] = style[key]) }
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
mouse.changed = true;
}
function mainLoop(){
var b = currentBox; // alias for readability
if(mouse.changed){
if(mouse.button){
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,boxStyle);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}else if(b){
b.draw(background.ctx);
b = currentBox = undefined;
}
if(b){
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
b.draw(ctx);
canvas.style.cursor = "none";
}else{
canvas.style.cursor = "crosshair";
}
mouse.changed = false;
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
Extra Note. Capture the mouse using the Document
When you create canvas drawing apps you should listen to the document mouse events rather than the canvas. When the mouse button is down the mouse is captured and will continue to send mouse events while the mouse is down, even if you have moved off the canvas, document, or event outside the browser window.
This means you can drag content of the canvas and not worry about losing the mouseup event.
Burn some time.
I have some time to burn so will extend the demo above to include selecting and moving existing boxes. Draw boxes as normal. Mouse over boxes will highlight them, click to select them. When selected can be dragged. Uses the same method background image to hold old boxes. But have added a box list to hold old boxes
A more extensive example
const canvas = document.createElement("canvas");
const background = document.createElement("canvas");
canvas.style.border="2px solid black";
canvas.style.cursor = "crosshair";
background.width = canvas.width = innerWidth - 24;
background.height = canvas.height = innerHeight - 24;
const ctx = canvas.getContext("2d");
background.ctx = background.getContext("2d");
document.body.appendChild(canvas);
const bounds = canvas.getBoundingClientRect();
var currentBox;
var selectedBox;
var mouseOverBox;
const styles = {
box : {
fillStyle : "#4aF",
strokeStyle : "black",
lineWidth : 3,
lineJoin : "round",
},
highlight : {
strokeStyle : "white",
lineWidth : 1,
lineJoin : "round",
setLineDash : [[10,10]],
},
selected : {
strokeStyle : "red",
lineWidth : 2,
lineJoin : "round",
setLineDash : [[5,5]],
},
}
const boxes = {
items : [],
add(box){ // add a box and fix width and height to positive
if(box.w < 0){
box.x += box.w;
box.w = -box.w;
}
if(box.h < 0){
box.y += box.h;
box.h = -box.h;
}
boxes.items.push(box)
},
apply(name, ...args){
for(var i = 0; i < boxes.items.length; i ++ ){
boxes.items[i][name](...args);
}
},
};
const mouse = { x : 0, y : 0,button : false, changed : false };
["mousemove","mousedown","mouseup"].forEach(en => document.addEventListener(en, mouseEvent));
const boxBehaviours = {
draw(ctx, style = this.style){
if(!this.hide){
setStyle(ctx, style);
ctx.beginPath();
ctx.rect(this.x,this.y,this.w,this.h);
if(style.fillStyle) { ctx.fill() }
if(style.strokeStyle) {ctx.stroke() }
}
},
isPointOver(x,y){
var b = this;
if(x >= b.x && x < b.x + b.w && y >= b.y && y < b.y + b.h){
b.mouseOver = true;
boxBehaviours.topMouseBox = b;
}else {
b.mouseOver =false;
}
},
}
function createBox(x,y,w,h,style){
return {x,y,w,h,style, ...boxBehaviours};
}
function setStyle(ctx, style){
Object.keys(style).forEach(key => {
if(typeof ctx[key] === "function"){
ctx[key](...style[key]);
}else{
ctx[key] = style[key];
}
})
}
function mouseEvent(event) {
mouse.x = event.pageX - bounds.left - scrollX;
mouse.y = event.pageY - bounds.top - scrollY;
if(event.type === "mousedown"){ mouse.button = true }
else if(event.type === "mouseup"){ mouse.button = false }
}
function redrawBackground(){
background.ctx.clearRect(0,0,canvas.width,canvas.height)
boxes.apply("draw",background.ctx);
}
function mainLoop(time){
var b = currentBox; // alias for readability
var mob = mouseOverBox; // alias for readability
var sb = selectedBox; // alias for readability
// first check mouse button. If button down could be
// dragging a selected box or creating a new box
if(mouse.button){
if(sb){ // is selected box
if(!mouse.drag){ // start the drag
mouse.drag = {x : mouse.x - sb.x, y : mouse.y - sb.y}
}else{ // move the box
sb.x = mouse.x- mouse.drag.x;
sb.y = mouse.y- mouse.drag.y;
}
}else{ // else muse be create (or select click)
if(!b){
b = currentBox = createBox(mouse.x,mouse.y,0,0,styles.box);
}else{
b.w = mouse.x - b.x;
b.h = mouse.y - b.y;
}
}
}else if(b || sb){ // mouse up and there is a box
if(sb){ // if selected box
if(mouse.drag){ // is dragging then drop it
mouse.drag = undefined;
sb.hide = false;
redrawBackground();
sb = selectedBox = undefined;
}
// is the mouse is down and has not moved over 2 pixels
// and there is a mob (mouseOverBox) under it
// then dump the new box and select the mob box
}else if(Math.abs(b.w) < 2 && Math.abs(b.h) < 2 && mob){
sb = selectedBox = mob;
mob = mouseOverBox = undefined;
b = currentBox = undefined;
sb.hide = true;
redrawBackground();
}else{
// just a normal box add it to box array
// draw it and remove it from currentBox
boxes.add(b);
b.draw(background.ctx);
b = currentBox = undefined;
}
}
// clear andf draw background
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(background,0,0);
if(b){ // is there a current box then draw that
b.draw(ctx);
canvas.style.cursor = "none";
} else { // no current box so
// find any boxes under the mouse
boxBehaviours.topMouseBox = null;
boxes.apply("isPointOver",mouse.x, mouse.y);
// is there a selected box (sb)
if(sb){ // yes selected box then draw it
ctx.save();
styles.selected.lineDashOffset = time / 25;
sb.hide = false;
sb.draw(ctx,styles.selected);
sb.hide = true;
ctx.restore();
canvas.style.cursor = "move";
// no selected box sp then just high light the box under the
// mouse and assign it to mouseOverBox (mob);
}else if(boxBehaviours.topMouseBox){
mob = mouseOverBox = boxBehaviours.topMouseBox;
ctx.save();
styles.highlight.lineDashOffset = time / 20;
mob.draw(ctx, styles.highlight);
ctx.restore();
canvas.style.cursor = "pointer";
}else{
canvas.style.cursor = "crosshair";
}
}
requestAnimationFrame(mainLoop);
}
requestAnimationFrame(mainLoop);
var point = [];
var clicks = 0;
var sketch = document.querySelector('#sketch');
var sketch_style = getComputedStyle(sketch);
// Creating a tmp canvas
var tmp_canvas = document.createElement('canvas');
var tmp_ctx = tmp_canvas.getContext('2d');
tmp_canvas.id = 'tmp_canvas';
tmp_canvas.width = parseInt(sketch_style.getPropertyValue('width'));
tmp_canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(tmp_canvas);
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.id = 'paint';
canvas.width = parseInt(sketch_style.getPropertyValue('width'));
canvas.height = parseInt(sketch_style.getPropertyValue('height'));
sketch.appendChild(canvas);
tmp_canvas.addEventListener('mousedown', mousedown, false);
tmp_canvas.addEventListener('mousemove', mousemove, false);
tmp_canvas.addEventListener('mouseup', mouseup, false);
function mousemove(e) {
if (clicks == 1) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
showRect(x, y);
}
}
function showRect(x, y) {
tmp_ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
tmp_ctx.beginPath();
var width = x - point[0].x;
var height = y - point[0].y;
tmp_ctx.rect(point[0].x, point[0].y, width, height);
tmp_ctx.stroke();
}
function mousedown(e) {
x = e.layerX - this.offsetLeft;
y = e.layerY - this.offsetTop;
point.push({
x,
y
});
clicks++;
};
function mouseup() {
context.drawImage(tmp_canvas, 0, 0);
clicks = 0;
point.length = 0;
}
html, body {
width: 100% ;
height: 100% ;
}
#sketch {
border: 10px solid gray;
height: 100% ;
position: relative;
}
#tmp_canvas {
position: absolute;
left: 0px;
right: 0;
bottom: 0;
top: 0;
cursor: crosshair;
}
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<div id="sketch">
</div>
</body>
</html>
Try to do in temporary canvas and redraw all in main.
jsfiddle:-https://jsfiddle.net/durga598/v0m06faz/
I'm assuming that the foo() function is being called for every frame, either through setInterval or requestAnimationFrame. If my assumption is right, the reason why your previously drawn square disappears is because you are only storing the x and y coordinates of one rectangle, and every time you click on the canvas again, it gets overwritten by the new values for the new rectangle.
To solve your problem, you should store the x and y coordinates as well as the dimensions of the square on mouseup. These coordinates can be stored in an array.
var squares = [];
this.mouseup = function (ev) {
// other code
var square = {
x: last_mousex,
y: last_mousey,
width: mousex - last_mousex,
height: mousey - last_mousey
};
squares.push(square);
};
Now every time you draw the square, draw the squares stored in the squares array first.
this.mousemove = function (ev) {
if (tool.started && checkboxSquare.checked) {
// other code
context.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
// draw each square in the squares array after clearning the canvas
squares.forEach(function(square) {
context.beginPath();
context.rect(square.x, square.y, square.width, square.height);
});
context.beginPath();
var width = mousex - last_mousex;
var height = mousey - last_mousey;
context.rect(last_mousex, last_mousey, width, height);
context.stroke();
}
};
You'll see some code repetitions in drawing the squares, it's a good opportunity to abstract it into a separate function.
I have 2 elements - video and canvas. On video play event, a functions draws the same video on canvas only greyscale. Then I have a button which is supposed to fade canvas video from greyscale back to color. So far I've managed to get back colors on button click, but I need it to fade - from greyscale to color, not just instantly show color.
Any ideas on how could I accomplish that? Or.. is it even possible?
Here's the code:
function grey() {
if (!stop) {
bgContext.drawImage(video, 0, 0, w, h);
var pixelData = bgContext.getImageData(0, 0, w, h);
for (var i = 0; i < pixelData.data.length; i += 4 ) {
var r = pixelData.data[i];
var g = pixelData.data[i+1];
var b = pixelData.data[i+2];
var averageColour = (r + g + b) / 3;
pixelData.data[i] = averageColour;
pixelData.data[i+1] = averageColour;
pixelData.data[i+2] = averageColour;
}
context.putImageData(pixelData, 0, 0);
}
}
function color() {
bgContext.drawImage(video, 0, 0, w, h);
var pixelData = bgContext.getImageData(0, 0, w, h);
for (var i = 0; i < pixelData.data.length; i += 4 ) {
var r = pixelData.data[i];
var g = pixelData.data[i+1];
var b = pixelData.data[i+2];
pixelData.data[i] = r;
pixelData.data[i+1] = g;
pixelData.data[i+2] = b;
}
context.putImageData(pixelData, 0, 0);
}
video.addEventListener('play', function() {
setInterval("grey()", 0);
}, false);
button.addEventListener('click', function() {
stop = true;
setInterval("color()", 0);
}, false);
Canvas Filters for real-time Video/Animation.
Black & White filter
To do a black and white filter is easy.
// mixAmount is a value from 0 - 1 0 = no mix 1 = full FX
// video is the video
ctx.drawImage(video,0,0); // draw the video
// set up filter
ctx.fillStyle = "#888"; // gray colour
ctx.globalAlpha = mixAmount; // amount of FX
ctx.globalCompositeOperation = "color"; // The comp setting to do BLACK/WHITE
ctx.fillRect(0,0,video.width,video.height); // Draw gray over the video
ctx.globalAlpha = 1; // reset alpha
ctx.globalCompositeOperation = "source-over"; // reset comp
Or you can render the video over itself to get other FX, the demo shows the Black and White filter and several more by just using the above code and a few extra layers.
More info
For more on displaying a video see Display video inside canvas
Demo
The demo show how to do Black and white and some other FX while I am at it.
How to use.
See video title for attribution. FX on left from top to bottom "Lighten", "Black & white", "Sepia", "Saturate", and "Negative".
The Demo has the following FX Lighter, Darken, Black/White, Negative, Saturate, Sepia, B&W negative, and more.
The Javascript
The code relating to the question is all at the top and marked. The rest is UI loading etc..
Each FX is a function that will call either addMix or addOverlay to apply the filter as shown in the snippet above. The addMix function is slightly different as it draws the video over the video to get the FX rather than a fill.
Instructions are on the demo.
Please note not all browser support all comp modes (WHY?? who knows!! :( ) Nor is there a way to be 100% sure if a browser supports a mode or not. The safe bet is Firefox, Chrome, and Edge for all other browsers best of luck..
//==========================================================================
// All the mix function are in this section
var FXMix = 1;
var addOverlay = function(type, repeat = 1){
if(FXMix > 0){
ctx.globalCompositeOperation = type;
ctx.globalAlpha = FXMix;
while (repeat-- > 0) {
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
}
var addMix = function(type,video, repeat = 1){
if(FXMix > 0){
ctx.globalCompositeOperation = type;
ctx.globalAlpha = FXMix;
while (repeat-- > 0) {
ctx.drawImage(video,0, 0, canvas.width, canvas.height);
}
ctx.globalAlpha = 1;
ctx.globalCompositeOperation = "source-over";
}
}
var fill = function(style){
ctx.globalAlpha = FXMix;
ctx.fillStyle = style;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalAlpha = 1;
}
var FX = {
}
var FXList = [];
var currentFX = "";
var addFX = function(name,func){
FXList.push(name);
FX[name] = func;
currentFX = name;
}
// multiply,screen,overlay,color-dodge,color-burn,hard-light,soft-light,difference,exclusion,hue,saturation,color,luminosity
addFX("Ligher",(vid)=>{ addMix("lighter",vid);} );
addFX("BlackWhite",(vid)=>{ ctx.fillStyle = "#888"; addOverlay("color");} );
addFX("Negative",(vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference");} );
addFX("Sepia",(vid)=>{ fill("#F94"); addMix("luminosity",vid); ;} );
addFX("B&W Negative",(vid)=>{ ctx.fillStyle = "#FFF"; addOverlay("difference");ctx.fillStyle = "#888"; addOverlay("color");} );
addFX("Ligher+",(vid)=>{ addMix("lighter",vid);addMix("lighter",vid);addMix("lighter",vid);} );
addFX("B&W Lighten",(vid)=>{ addMix("lighter",vid);ctx.fillStyle = "#888"; addOverlay("color");} );
addFX("Darken+",(vid)=>{ addMix("multiply",vid);addMix("multiply",vid);addMix("multiply",vid);} );
addFX("Darken",(vid)=>{ addMix("multiply",vid);} );
addFX("Saturate",()=>{ ctx.fillStyle = "#F00";addOverlay("saturation");});
addFX("Movement",(vid) => {
const keepMix = FXMix;
FXMix = 1;
addMix("difference",can1);
addMix("lighter",ctx.canvas,2);
addMix("multiply",vid,1);
FXMix = keepMix * 0.95;
addMix("screen",can2,1);
can2.ctx.drawImage(ctx.canvas,0,0,canvas.width, canvas.height);
FXMix = 1;
addMix("lighter",ctx.canvas,1);
FXMix = keepMix;
var scale = videoContainer.scale;
var vidH = vid.videoHeight;
var vidW = vid.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
if(can1.counting === undefined) { can1.counting = 0 }
else { can1.counting ++ }
if(can1.counting % 2 === 0) {
can1.ctx.drawImage(vid, left, top, vidW * scale, vidH * scale);
}
});
addFX("None",()=>{});
// end of FX mixing
//==========================================================================
var mediaSource = "http://video.webmfiles.org/big-buck-bunny_trailer.webm";
var mediaSource = "http://upload.wikimedia.org/wikipedia/commons/7/79/Big_Buck_Bunny_small.ogv";
var muted = true;
var canvas = document.getElementById("myCanvas"); // get the canvas from the page
var ctx = canvas.getContext("2d");
const can1 = document.createElement("canvas");
can1.width = canvas.width;
can1.height = canvas.height;
can1.ctx = can1.getContext("2d");
const can2 = document.createElement("canvas");
can2.width = canvas.width;
can2.height = canvas.height;
can2.ctx = can2.getContext("2d");
var videoContainer; // object to hold video and associated info
var video = document.createElement("video"); // create a video element
video.src = mediaSource;
// the video will now begin to load.
// As some additional info is needed we will place the video in a
// containing object for convenience
video.autoPlay = false; // ensure that the video does not auto play
video.loop = true; // set the video to loop.
video.muted = muted;
videoContainer = { // we will add properties as needed
video : video,
ready : false,
};
// To handle errors. This is not part of the example at the moment. Just fixing for Edge that did not like the ogv format video
video.onerror = function(e){
document.body.removeChild(canvas);
document.body.innerHTML += "<h2>There is a problem loading the video</h2><br>";
document.body.innerHTML += "Users of IE9+ , the browser does not support WebM videos used by this demo";
document.body.innerHTML += "<br><a href='https://tools.google.com/dlpage/webmmf/'> Download IE9+ WebM support</a> from tools.google.com<br> this includes Edge and Windows 10";
}
video.oncanplay = readyToPlayVideo; // set the event to the play function that
// can be found below
function readyToPlayVideo(event){ // this is a referance to the video
// the video may not match the canvas size so find a scale to fit
videoContainer.scale = Math.min(
canvas.width / this.videoWidth,
canvas.height / this.videoHeight);
videoContainer.ready = true;
// the video can be played so hand it off to the display function
requestAnimationFrame(updateCanvas);
// add instruction
document.getElementById("playPause").textContent = "Click video to play/pause.";
document.querySelector(".mute").textContent = "Mute";
}
var playClick = false;
function updateCanvas(){
ctx.clearRect(0,0,canvas.width,canvas.height);
// only draw if loaded and ready
if(videoContainer !== undefined && videoContainer.ready){
// find the top left of the video on the canvas
video.muted = muted;
var scale = videoContainer.scale;
var vidH = videoContainer.video.videoHeight;
var vidW = videoContainer.video.videoWidth;
var top = canvas.height / 2 - (vidH /2 ) * scale;
var left = canvas.width / 2 - (vidW /2 ) * scale;
// now just draw the video the correct size
ctx.drawImage(videoContainer.video, left, top, vidW * scale, vidH * scale);
FX[currentFX](videoContainer.video);
if(videoContainer.video.paused){ // if not playing show the paused screen
drawPayIcon();
}
overUI = false;
cursor = "default";
drawSlider();
drawList();
if(mouse.over){
if(!overUI){
if((mouse.button&1)===1){ // bit field
playClick = true;
}
if((mouse.button&1)===0 && playClick){ // bit field
playClick = false;
playPauseClick();
}
cursor = "pointer";
}
}
if(showFXName > 0){
showFXName = Math.max(0,showFXName - 0.05);
ctx.globalAlpha = Math.min(1,showFXName);
ctx.font = "32px Arial";
ctx.textAlign = "center";
ctx.textbaseLine = "middle";
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.lineJoin = "round"
ctx.strokeText(currentFX,canvas.width/2,canvas.height/2);
ctx.fillText(currentFX,canvas.width/2,canvas.height/2);
ctx.globalAlpha = 1;
}
canvas.style.cursor = cursor;
}
// all done for display
// request the next frame in 1/60th of a second
requestAnimationFrame(updateCanvas);
}
var showFXName = 0;
var cursor = "default";
var overUI = false;
var sliderAlpha = 1;
var listAlpha = 1;
var dragging = false;
var listWidth = null;
function getMaxListWidth(){
ctx.font = "12px arial";
FXList.forEach(text => {listWidth = Math.max(listWidth,ctx.measureText(text).width)})
}
function drawList(){
if(listWidth === null){
getMaxListWidth();
listWidth += 10;
}
if(!overUI && mouse.over && mouse.x > canvas.width - listWidth){
listAlpha = 1;
overUI = true;
}else{
listAlpha = Math.max(0,listAlpha - 0.05);
}
if(listAlpha > 0){
ctx.font = "12px arial";
var textH = 14;
var border = 10;
ctx.textAlign = "right";
ctx.textBaseline = "middle";
ctx.globalAlpha = listAlpha;
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
var len = FXList.length;
var h = len * textH;
var y = canvas.height / 2 - h/2;
var x = canvas.width - border * 2;
ctx.fillRect(x - listWidth,y - border, listWidth+border,h + border );
ctx.strokeRect(x - listWidth,y - border, listWidth + border,h + border );
ctx.fillStyle = "white"
for(var i = 0; i < len; i ++){
var yy = y + i * textH;
if(FXList[i] === currentFX){
ctx.fillStyle = "#0FF";
ctx.fillText(FXList[i],x,yy);
ctx.fillStyle = "white"
}else
if(mouse.x > canvas.width - listWidth && mouse.y > yy - textH/2 && mouse.y < yy + textH /2){
ctx.fillStyle = "#0F0";
ctx.fillText(FXList[i],x,yy);
ctx.fillStyle = "white"
cursor = "pointer";
if((mouse.button & 1) === 1){
currentFX =FXList[i];
showFXName = 4;
}
}else{
ctx.fillText(FXList[i],x,yy);
}
}
ctx.globalAlpha = 1;
}
}
function drawSlider(){
if(currentFX === "None"){
sliderAlpha = 0;
return;
}
var cw = canvas.width;
var ch = canvas.height;
var handle = 5;
var inset = 10
var x = inset;
var w = cw - inset*2;
var h = 20;
var y = ch - inset - h;
var pos = FXMix * w + x;;
if(mouse.y > y - h* 2){
cursor = "e-resize";
overUI = true;
if((mouse.button&1) && !dragging){ // bit field
dragging = true;
}
}else{
cursor = "pointer";
}
if(dragging){
overUI = true;
cursor = "e-resize";
sliderAlpha = 1;
pos = mouse.x - x;
FXMix = Math.min(1,Math.max(0,pos / w));
if( (mouse.button&1) === 0 ){ //bit field
dragging = false;
}
}else{
}
if(!dragging && mouse.y > y-h*2 && mouse.over){
sliderAlpha = 1;
}else{
if(sliderAlpha > 0){
sliderAlpha = Math.max(0,sliderAlpha- 0.05);
}
}
if(sliderAlpha === 0){
return;
}
ctx.globalAlpha = sliderAlpha;
ctx.font = "18px arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var amount = FXMix;
ctx.fillStyle = "black";
ctx.strokeStyle = "white";
ctx.fillRect(x,y,w,h);
ctx.strokeRect(x,y,w,h);
ctx.fillStyle = "white";
ctx.fillText(currentFX + " "+ (FXMix * 100).toFixed(0)+"%",w/2,y + h / 2);
pos = amount * w + x;
ctx.fillStyle = "white";
ctx.strokeStyle = "black";
ctx.fillRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
ctx.strokeRect(pos-handle*2,y-handle,handle* 4,h + handle * 2);
ctx.strokeRect(pos-1,y-handle * 0.5,2,h + handle);
ctx.globalAlpha = 1;
}
function drawPayIcon(){
// ctx.fillStyle = "black"; // darken display
// ctx.globalAlpha = 0.5;
// ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "#DDD"; // colour of play icon
ctx.globalAlpha = 0.75; // partly transparent
ctx.beginPath(); // create the path for the icon
var size = (canvas.height / 2) * 0.5; // the size of the icon
ctx.moveTo(canvas.width/2 + size/2, canvas.height / 2); // start at the pointy end
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 + size);
ctx.lineTo(canvas.width/2 - size/2, canvas.height / 2 - size);
ctx.closePath();
ctx.fill();
ctx.globalAlpha = 1; // restore alpha
}
mouse = (function(){
var mouse = {
x : 0, y : 0, w : 0,
button : 0,
over : false,
bm : [1, 2, 4, 6, 5, 3],
active : false,
bounds : null,
border : {top : 10, left : 10},
mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,contextmenu".split(",")
};
var m = mouse;
function mouseMove(e) {
var t = e.type;
m.bounds = m.element.getBoundingClientRect();
m.x = e.clientX - m.bounds.left - m.border.left;
m.y = e.clientY - m.bounds.top - m.border.top;
if (t === "mousedown") {
m.button |= m.bm[e.which-1];
} else if (t === "mouseup") {
m.button &= m.bm[e.which + 2];
}else if (t === "mouseout") {
m.button = 0;
m.over = false;
}else if (t === "mouseover") {
m.over = true;
}
e.preventDefault();
}
m.start = function (element) {
m.element = element;
m.mouseEvents.forEach( n => { m.element.addEventListener(n, mouseMove); } );
m.active = true;
//m.border.top = Number(element.style.borderTopWidth.replace(/[a-zA-Z]/g,""));
//m.border.left = Number(element.style.borderLeftWidth.replace(/[a-zA-Z]/g,""));
}
m.remove = function () {
if (m.element !== undefined) {
m.mouseEvents.forEach(n => { m.element.removeEventListener(n, mouseMove); } );
m.active = false;
m.element = undefined;
}
}
return mouse;
})();
function playPauseClick(){
if(videoContainer !== undefined && videoContainer.ready){
if(videoContainer.video.paused){
videoContainer.video.play();
}else{
videoContainer.video.pause();
}
}
}
function videoMute(){
muted = !muted;
if(muted){
document.querySelector(".mute").textContent = "Mute";
}else{
document.querySelector(".mute").textContent= "Sound on";
}
}
// register the event
//canvas.addEventListener("click",playPauseClick);
document.querySelector(".mute").addEventListener("click",videoMute)
setTimeout(()=>{mouse.start(canvas)},100);
body {
font :14px arial;
text-align : center;
background : #36A;
}
h2 {
color : white;
}
canvas {
border : 10px white solid;
cursor : pointer;
}
a {
color : #F93;
}
.mute {
cursor : pointer;
display: initial;
}
<h2>Simple video FX via canvas "globalCompositeOperation"</h2>
<p>This example show how to use the 2d context "globalCompositeOperation" property to create a variety of FX. Video may take a few moment to load.
</p>
<p>Play pause video with click. Move to bottom of video to see FX mix slider (Not available if filter None). Move to right to get filter selection and select the filter example. Happy filtering</p>
<canvas id="myCanvas" width = "532" height ="300" ></canvas><br>
<h3><div id = "playPause">Loading content.</div></h3>
<div class="mute"></div><br>
Update 30 Sep 2019
Add filter "Movement" that highlight the change per frame (movement). Slider changes the persistence of the highlighted changes.
The simplest is to set a greyscale css filter on the canvas.
var video = document.getElementById("myCanvas");
var button = document.getElementById("myButton");
function grey() {
video.className += " greyscale";
}
function color() {
classGreyscale = video.className.indexOf("greyscale");
if (classGreyscale > 0)
video.className = video.className.substring(0, video.className.length - 10)
}
button.addEventListener('click', function() {
color();
});
grey();
.greyscale {
-webkit-filter: grayscale(100%);
filter: grayscale(100%);
}
.transition {
transition: all 1s;
-webkit-transition: all 1s;
}
<div>
<img src="https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png" id="myCanvas" class="transition" />
<br/>
<button id="myButton">to Colour</button>
</div>
So what we do here is add a transition class for our canvas, so it can animate the changes. Then when we add a grayscale class to it and this changes a css filter along with the transition so it fades in. When we want to make it colorful again we remove the greyscale class.
I made the example with an image but it will work with everything.
This way you fade in the class with 1s transition on all parameters (greyscale here). It is better to use this, because you don't have to count every pixel in grayscale, it is cleaner.
Note that you could use jQuery addClass removeClass for simpler and cleaner soultion.
Also note that you should average r,g,b with weights:
https://en.wikipedia.org/wiki/Grayscale#Colorimetric_.28luminance-preserving.29_conversion_to_grayscale
Saturation version with transition and -webkit-transition: -webkit-filter 10s;
This is only for Safari and Chrome.
The code is for hovering.
I guess very similar to #godzsa
The other method I can think of is to create a div with a higher index on top of your video with a play on white saturation.
For working Youtube video,
https://jsfiddle.net/yd215t9p/
And for image,
div {
-webkit-filter:saturate(0.0);
-webkit-transition: -webkit-filter 10s; /* Safari */
transition: -webkit-filter 10s;
}
div:hover {
-webkit-filter:saturate(1.0);
}
<div>
<img src="https://upload.wikimedia.org/wikipedia/en/2/24/Lenna.png" id="myCanvas" class="transition" />
</div>