HTML canvas spotlight effect - javascript

Let's say I have the following code.
// Find out window height and width
wwidth = $(window).width();
wheight = $(window).height();
// Place Canvas over current Window
$("body").append($("<canvas id='test' style='position:absolute; top:0; left:0;'></canvas>"));
var context = document.getElementById("test").getContext("2d");
context.canvas.width = wwidth;
context.canvas.height = wheight;
// Paint the canvas black.
context.fillStyle = '#000';
context.clearRect(0, 0, context.canvas.width, context.canvas.height);
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
// On Mousemove, create "Flashlight" around the mouse, to see through the canvas
$(window).mousemove(function(event){
x = event.pageX;
y = event.pageY;
radius = 50;
context = document.getElementById("test").getContext("2d");
// Paint the canvas black. Instead it will draw it white?!
//context.fillStyle = '#000';
//context.clearRect(0, 0, context.canvas.width, context.canvas.height);
//context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
radialGradient = context.createRadialGradient(x, y, 1, x, y, radius);
radialGradient.addColorStop(0, 'rgba(255,255,255,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
context.globalCompositeOperation = "destination-out";
context.fillStyle = radialGradient;
context.arc(x, y, radius, 0, Math.PI*2, false);
context.fill();
context.closePath();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div>Test</div>
which generates the following effect on mousemove:
How do I refill the canvas with black before the spotlight is drawn? I have already tried with what is in the commented-out code block, but it paints everything white.
EDIT: I dont want this effect over an image. Instead i would like to place the Canvas over the whole Webpage. ALso I want the Canvas to be always black and the mouse generates a Spotlight over its position, to see what is under the Canvas just as u can see in the picture, or in the Snippet where a div was placed in an empty html page with "Test" in it.

You can use compositing to create your flashlight effect:
Clear the canvas
Create a radial gradient to use as a reveal.
Fill the radial gradient.
Use source-atop compositing to draw the background image. The image will display only inside the radial gradient.
Use destination-over compositing to fill the canvas with black. The black will fill "behind" the existing radial-gradient-image.
Here's example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
$("#canvas").mousemove(function(e){handleMouseMove(e);});
var radius=30;
var img=new Image();
img.onload=function(){
draw(150,150,30);
}
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg'
function draw(cx,cy,radius){
ctx.save();
ctx.clearRect(0,0,cw,ch);
var radialGradient = ctx.createRadialGradient(cx, cy, 1, cx, cy, radius);
radialGradient.addColorStop(0, 'rgba(0,0,0,1)');
radialGradient.addColorStop(.65, 'rgba(0,0,0,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
ctx.beginPath();
ctx.arc(cx,cy,radius,0,Math.PI*2);
ctx.fillStyle=radialGradient;
ctx.fill();
ctx.globalCompositeOperation='source-atop';
ctx.drawImage(img,0,0);
ctx.globalCompositeOperation='destination-over';
ctx.fillStyle='black';
ctx.fillRect(0,0,cw,ch);
ctx.restore();
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
draw(mouseX,mouseY,30);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse to reveal image with "flashlight"</h4>
<canvas id="canvas" width=300 height=300></canvas>
If your spotlight radius will never change, here's a much faster method:
The speed is gained by caching the spotlight to a second canvas and then...
Draw the image on the canvas.
Draw the spotlight on the canvas.
Use fillRect to black out the 4 rectangles outside the spotlight.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
var radius=50;
var cover=document.createElement('canvas');
var cctx=cover.getContext('2d');
var size=radius*2+10;
cover.width=size;
cover.height=size;
cctx.fillRect(0,0,size,size);
var radialGradient = cctx.createRadialGradient(size/2, size/2, 1, size/2, size/2, radius);
radialGradient.addColorStop(0, 'rgba(0,0,0,1)');
radialGradient.addColorStop(.65, 'rgba(0,0,0,1)');
radialGradient.addColorStop(1, 'rgba(0,0,0,0)');
cctx.beginPath();
cctx.arc(size/2,size/2,size/2,0,Math.PI*2);
cctx.fillStyle=radialGradient;
cctx.globalCompositeOperation='destination-out';
cctx.fill();
var img=new Image();
img.onload=function(){
$("#canvas").mousemove(function(e){handleMouseMove(e);});
ctx.fillRect(0,0,cw,ch);
}
img.src='https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg'
function drawCover(cx,cy){
var s=size/2;
ctx.clearRect(0,0,cw,ch);
ctx.drawImage(img,0,0);
ctx.drawImage(cover,cx-size/2,cy-size/2);
ctx.fillStyle='black';
ctx.fillRect(0,0,cx-s,ch);
ctx.fillRect(0,0,cw,cy-s);
ctx.fillRect(cx+s,0,cw-cx,ch);
ctx.fillRect(0,cy+s,cw,ch-cy);
}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
drawCover(mouseX,mouseY);
}
body{ background-color: ivory; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<h4>Move mouse to reveal image with "flashlight"</h4>
<canvas id="canvas" width=300 height=300></canvas>

You can achieve the spotlight effect by positioning a canvas directly over the image. Make the canvas the same size as the image and set the compositing operation to xor so that two black pixels drawn in the same place cancel each other out.
context.globalCompositeOperation = 'xor';
Now you can paint the canvas black and fill a black circle around the mouse cursor. The result is a hole in the black surface, showing the image underneath.
// Paint the canvas black.
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
// Paint a black circle around x, y.
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
// With xor compositing, the result is a circular hole.
To make a spotlight with blurry edges, define a radial gradient centered on the mouse position and fill a square around it.
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.9, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
The following snippet demonstrates both approaches using pure JavaScript. To change from a crisp-edged spotlight to a blurry-edged spotlight, click on the checkbox above the image.
function getOffset(element, ancestor) {
var left = 0,
top = 0;
while (element != ancestor) {
left += element.offsetLeft;
top += element.offsetTop;
element = element.parentNode;
}
return { left: left, top: top };
}
function getMousePosition(event) {
event = event || window.event;
if (event.pageX !== undefined) {
return { x: event.pageX, y: event.pageY };
}
return {
x: event.clientX + document.body.scrollLeft +
document.documentElement.scrollLeft,
y: event.clientY + document.body.scrollTop +
document.documentElement.scrollTop
};
}
window.onload = function () {
var spotlightRadius = 60,
container = document.getElementById('container'),
canvas = document.createElement('canvas'),
image = container.getElementsByTagName('img')[0],
width = canvas.width = image.width,
height = canvas.height = image.height,
context = canvas.getContext('2d');
context.globalCompositeOperation = 'xor';
container.insertBefore(canvas, image.nextSibling);
container.style.width = width + 'px';
container.style.height = height + 'px';
var offset = getOffset(canvas, document.body);
clear = function () {
context.fillStyle = '#000';
context.clearRect(0, 0, width, height);
context.fillRect(0, 0, width, height);
};
clear();
image.style.visibility = 'visible';
canvas.onmouseout = clear;
canvas.onmouseover = canvas.onmousemove = function (event) {
var mouse = getMousePosition(event),
x = mouse.x - offset.left,
y = mouse.y - offset.top;
clear();
if (document.getElementById('blurry').checked) {
var gradient = context.createRadialGradient(x, y, 0, x, y, spotlightRadius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(0.875, 'rgba(0, 0, 0, 1)');
gradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
context.fillStyle = gradient;
context.fillRect(x - spotlightRadius, y - spotlightRadius,
2 * spotlightRadius, 2 * spotlightRadius);
} else {
context.beginPath();
context.arc(x, y, spotlightRadius, 0, 2 * Math.PI);
context.fillStyle = '#000';
context.fill();
}
};
};
* {
margin: 0;
padding: 0;
}
.control {
font-family: sans-serif;
font-size: 15px;
padding: 10px;
}
#container {
position: relative;
}
#container img, #container canvas {
position: absolute;
left: 0;
top: 0;
}
#container img {
visibility: hidden;
}
#container canvas {
cursor: none;
}
<p class="control">
<input type="checkbox" id="blurry" /> blurry edges
</p>
<div id="container">
<img src="https://dl.dropboxusercontent.com/u/139992952/multple/annotateMe.jpg" />
</div>

this code works for me:
x = event.pageX;
y = event.pageY;
radius = 10;
context = canvas.getContext("2d");
context.fillStyle = "black";
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
context.beginPath();
var radialGradient= context.createRadialGradient(x,y,1,x,y,radius);
radialGradient.addColorStop(0,"rgba(255,255,255,1");
radialGradient.addColorStop(1,"rgba(0,0,0,1)");
//context.globalCompositeOperation = "destination-out";
context.fillStyle = radialGradient;
context.arc(x, y, radius, 0, Math.PI*2, false);
context.fill();
context.closePath();
it seems that this line was messing it context.globalCompositeOperation = "destination-out";
there were also pointless lines in your code like beginnig path before filling rect and fill() function after filling path

Related

HTML5 canvas createPattern API

I have code like:
<body>
<canvas id="main" width=400 height=300></canvas>
<script>
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "redball.png";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = pattern;
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
context.fill();
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
</script>
</body>
I call createPattern API, use a background image to fill a Triangle, but when mouse move, the background image also move, I only want the background image at fixed position, how Can I fix this?
Think of a context pattern as a background image on the canvas.
Patterns always begin at the canvas origin [0,0]. If the pattern repeats, then the pattern fills the canvas in tiles repeating rightward and downward.
Therefore, your triangle will always reveal a different portion of the pattern if you move the triangle around the canvas.
There are multiple ways of having your triangle always reveal the same portion of the pattern image.
Option#1 -- context.translate
Move the canvas origin from its default [0,0] position to your triangle position [loc.x,loc.y]. You can do this with canvas transformations. In particular, the translate command will move the origin. Moving the origin will also move the top-left starting position of your pattern so that the pattern always aligns the same way relative to your moving triangle:
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
// the origin [0,0] is now [loc.x,loc.y]
context.translate(loc.x,loc.y);
context.beginPath();
// you are already located at [loc.x,loc.y] so
// you don't need to add loc.x & loc.y to
// your drawing coordinates
context.moveTo(0,0);
context.lineTo(300,60);
context.lineTo(70,200);
context.closePath();
context.fill();
// always clean up! Move the origina back to [0,0]
context.translate(-loc.x,-loc.y);
}
A Demo using translate:
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
draw({x:0,y:0});
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
// the origin [0,0] is now [loc.x,loc.y]
context.translate(loc.x,loc.y);
context.beginPath();
// you are already located at [loc.x,loc.y] so
// you don't need to add loc.x & loc.y to
// your drawing coordinates
context.moveTo(0,0);
context.lineTo(300,60);
context.lineTo(70,200);
context.closePath();
context.fill();
// always clean up! Move the origina back to [0,0]
context.translate(-loc.x,-loc.y);
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>
Option#2 -- compositing
Use compositing instead of patterning to draw an image atop your triangle. Compositing is a method to control how new pixels to be drawn on the canvas will interact with already existing canvas pixels. In particular, source-atop compositing will cause any new pixels to only be drawn where the new pixel overlaps an existing non-transparent pixel. What you would do is draw your triangle in a solid color and then use source-atop compositing to draw your image only where the solid triangle pixels are:
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
// fill the triangle with a solid color
context.fill();
// set compositing to 'source-atop' so
// new drawings will only be visible if
// they overlap a solid color pixel
context.globalCompositeOperation='source-atop';
context.drawImage(image,loc.x,loc.y);
// always clean up! Set compositing back to its default value
context.globalCompositeOperation='source-over';
}
A Demo using compositing:
var windowToCanvas = function(canvas, x, y) {
var bbox = canvas.getBoundingClientRect();
return {
x: (x - bbox.left) * (canvas.width / bbox.width),
y: (y - bbox.top) * (canvas.height / bbox.height)
};
};
image = new Image();
image.src = "https://dl.dropboxusercontent.com/u/139992952/multple/jellybeans.jpg";
image.onload = function (e) {
var canvas = document.getElementById('main'),
context = canvas.getContext('2d');
var pattern = context.createPattern(image, "repeat");
context.fillStyle=pattern;
draw({x:0,y:0});
function draw(loc) {
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.moveTo(loc.x, loc.y);
context.lineTo(loc.x + 300, loc.y + 60);
context.lineTo(loc.x + 70, loc.y + 200);
context.closePath();
// fill the triangle with a solid color
context.fill();
// set compositing to 'source-atop' so
// new drawings will only be visible if
// they overlap a solid color pixel
context.globalCompositeOperation='source-atop';
context.drawImage(image,loc.x,loc.y);
// always clean up! Set compositing back to its default value
context.globalCompositeOperation='source-over';
}
canvas.onmousemove = function(e) {
var event = e || window.event,
x = event.x || event.clientX,
y = event.y || event.clientY,
loc = windowToCanvas(canvas, x, y);
draw(loc);
};
}
body{ background-color: ivory; }
#canvas{border:1px solid red;}
<h4>Move the mouse to move the triangle<br>The image is large, so be patient while it loads</h4>
<canvas id="main" width=600 height=600></canvas>
More Options...
There are more possible options, too. I'll mention some of them without giving code examples:
Create an img element of your filled triangle and use drawImage(img,loc.x,loc.y) to move that triangle-image around the canvas
Create a clipping region from your triangle. Clipping regions cause new drawings to only be displayed in the defined clipping region. In this case, the new drawImage would only be visible inside your triangle shape and would not be visible outside your triangle.
And more options that are less conventional...

How to fade out an item in canvas

I have a full screen canvas in my page. There are also some dive elements over this canvas. there is an circle element in the canvas that moves with the cursor everywhere in the page. However when the cursor arrives to the div element over the canvas, the circle shape stays in the last place it was on the canvas before arriving to the div.
DEMO: JSFIDDLE
Is ther any way that I can fade-out the circle shape when the cursor is over the div element and fade it in when it backs to the canvas?
Also is there any other effect rather than fading out? like making it small and then fade-out...
Here is the bit of code related to the circle:
function writeMessage(canvas, message, x, y) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
var pattern = context.createPattern(imageResized, 'no-repeat');//Use imageResized, not imageObj.
context.fillStyle = pattern;
context.fill();
context.fillStyle = 'black';
context.beginPath();
context.arc(x, y, 60, 0, 2 * Math.PI);
}
Well, you can always create your own fade function that gets called on mouseout (or mouseleave) event. Here's one I quickly built for you:
function fadeOut(canvas, message, x, y, amount) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
var pattern = context.createPattern(imageResized, 'no-repeat');
context.fillStyle = pattern;
context.fill();
context.font = '28pt Calibri';
context.fillStyle = 'black';
context.beginPath();
context.arc(x, y, amount, 0, 2 * Math.PI);
if (amount > 0) {
setTimeout(function(){
fadeOut(canvas, message, x, y, --amount);
}, 2);
}
else {
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
See it in action here: http://jsfiddle.net/2p9dn8ed/42/
Or in the snippet:
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var x = 0;
var y = 0;
var width = window.innerWidth;
var height = window.innerHeight;
var imageObj = new Image();
console.log(window.innerWidth + "----" + window.innerHeight);
//Create another canvas to darw a resized image to.
var imageResized = document.createElement('canvas');
imageResized.width = width;
imageResized.height = height;
//Wait for the original image to low to draw the resize.
imageObj.onload = function() {
//Find hoe mauch to scale the image up to cover.
var scaleX = width / imageObj.width;
var scaleY = height / imageObj.height;
var scaleMax = Math.max(scaleX, scaleY);
var ctx = imageResized.getContext('2d');
ctx.scale(scaleMax, scaleMax);
ctx.drawImage(imageObj, 0, 0);
};
imageObj.src = 'http://www.html5canvastutorials.com/demos/assets/darth-vader.jpg';
function writeMessage(canvas, message, x, y) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
var pattern = context.createPattern(imageResized, 'no-repeat');//Use imageResized, not imageObj.
context.fillStyle = pattern;
context.fill();
context.font = '28pt Calibri';
context.fillStyle = 'black';
//context.fillText(message, x, y);
context.beginPath();
context.arc(x, y, 60, 0, 2 * Math.PI);
//context.stroke();
//
}
function fadeOut(canvas, message, x, y, amount) {
var context = canvas.getContext('2d');
context.clearRect(0, 0, canvas.width, canvas.height);
var pattern = context.createPattern(imageResized, 'no-repeat');//Use imageResized, not imageObj.
context.fillStyle = pattern;
context.fill();
context.font = '28pt Calibri';
context.fillStyle = 'black';
//context.fillText(message, x, y);
context.beginPath();
context.arc(x, y, amount, 0, 2 * Math.PI);
//context.stroke();
//
if (amount > 0) {
setTimeout(function(){
fadeOut(canvas, message, x, y, --amount);
}, 2);
}
else {
context.clearRect(0, 0, canvas.width, canvas.height);
}
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function (evt) {
var mousePos = getMousePos(canvas, evt);
var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
writeMessage(canvas, message, mousePos.x, mousePos.y);
}, false);
canvas.addEventListener('mouseout', function(evt){
var mousePos = getMousePos(canvas, evt);
var message = 'Mouse position: ' + mousePos.x + ',' + mousePos.y;
console.log(1);
fadeOut(canvas, message, mousePos.x, mousePos.y, 60);
});
// Get the canvas element form the page
var canvas = document.getElementById("myCanvas");
/* Rresize the canvas to occupy the full page,
by getting the widow width and height and setting it to canvas*/
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
anvas, img {
display:block;
margin:1em auto;
border:1px solid black;
}
canvas {
background:url('../img/spiral_galaxy-1920x1080.jpg');
background-size: cover;
position: absolute;
left: 0; top: 0;
width: 100%; height: 100%;
z-index:-1;
}
div{
width:200px;
height:200px;
background:rgba(0,0,0,0.5);
position: fixed;
top: 20%;
left: 20%;
}
<canvas id="myCanvas" width="578" height="400"></canvas>
<div><h1>TEXT</h1></div>

how to make an object turn around a point in html5 canvas

The problem is demonstrated in the image below
In other words, how to exactly compute the x and y coordinates for every frame?
ctx.arc(x,y,radius,0,pi*2,false)
because after incrementing one coordinate, I have a problem how to compute the other
I tried this but doesn't work
var step=2,x=100,y=100,r=50,coordinates=[[x,y-r]];
for(var i=1;i <r;i+=step){
bx=x;
x+=step;
y=y-Math.sqrt(Math.pow(r,2)-Math.pow(bx-x,2))
coordinates[i]=[x,y];
}
a jsfiddle will be appreciated.
var canvas = document.querySelector("#c");
var ctx = canvas.getContext("2d");
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var speed = 10; // Lower is faster
function animate(t){
ctx.save();
ctx.clearRect (0 , 0 ,canvas.width ,canvas.height );
ctx.translate(canvas.width/2, canvas.height/2);
// First circle
ctx.beginPath();
ctx.arc(0, 0, 5, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
ctx.closePath();
// rotate + move along
ctx.rotate((t/speed)/100);
ctx.translate(100,0);
// Orbiting cirle
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI, false);
ctx.fillStyle = 'red';
ctx.fill();
ctx.closePath();
ctx.restore();
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
canvas {
border: 1px solid black;
}
<canvas id="c" width="512" height="512"></canvas>

How to draw doughnut with HTML5 canvas

I would like to draw doughnut within HTML5 canvas.If the background color of the canvas is a solid color, I was able to draw it. But it's gradient color, I can't draw it.
I would like to know how to draw the doughnut, when the backgroud color of the canvas is gradient color.
Like:
Source
This is my code:
function background(context, coordinate, properties) {
var x = coordinate.x //起始点x
, y = coordinate.y //起始点 y
, w = coordinate.w //宽度(终点-起始点之间的宽度)
, h = coordinate.h //高度(终点-起始点之间的高度)
, gradientFactor, gradientColor; //渐变因子, 渐变色
context.save();
switch( properties["background-fill-type"] ) {
case "solid":
context.fillStyle = properties["background-color"];
break;
case "gradient":
gradientFactor = properties["background-gradient-factor"];
gradientColor = context.createLinearGradient(x, y, x + w, y);
gradientColor.addColorStop(gradientFactor, properties["background-first-color"]);
gradientColor.addColorStop(1 - gradientFactor, properties["background-second-color"]);
context.fillStyle = gradientColor;
break;
case "image":
break;
}
context.fillRect(x, y, w, h);
context.restore();
}
If the background color of the canvas is solid color:
var context = canvas.getContext("2d")
, properties = {
"background-fill-type": "solid", //solid color
"background-color": "#FFFFFF",
"background-first-color": "#008B8B",
"background-second-color": "#F5DEB3",
"background-gradient-factor": 0.5,
"border-color": "#FFFFFF",
"border-thickness": 0
};
//draw canvas background (solid color)
background(context, {
x: 0,
y: 0,
w: properties["width"],
h: properties["height"]
}, properties);
//draw doughnut
context.save();
context.beginPath();
context.translate(centerX, centerY);
context.arc(0, 0, Radius, 0, dpi, false); //外部圆
context.fillStyle = "blue";
context.fill();
context.closePath();
context.beginPath();
context.arc(0, 0, radius, 0, dpi, false); //内部圆
context.fillStyle = properties["background-color"];
context.fill();
context.closePath();
context.restore();
If the background color of the canvas is gradient color:
var context = canvas.getContext("2d")
, properties = {
"background-fill-type": "gradient", //gradient color
"background-color": "#FFFFFF",
"background-first-color": "#008B8B",
"background-second-color": "#F5DEB3",
"background-gradient-factor": 0.5,
"border-color": "#FFFFFF",
"border-thickness": 0
};
//draw canvas background (gradient color)
background(context, {
x: 0,
y: 0,
w: properties["width"],
h: properties["height"]
}, properties);
//draw doughnut
context.save();
context.beginPath();
context.translate(centerX, centerY);
context.arc(0, 0, Radius, 0, dpi, false); //外部圆
context.fillStyle = "blue";
context.fill();
context.closePath();
context.beginPath();
context.arc(0, 0, radius, 0, dpi, false); //内部圆
//context.fillStyle = properties["background-color"];
// *----------------------------------------------------------------------*
// | How to solve internal circle and canvas background color consistent? |
// |
// *----------------------------------------------------------------------*
context.fill();
context.closePath();
context.restore();
This is an effect diagram.( A little crooked, - -! ):
Drawing a data-donut with gradient background
Your donut is just a circle with the center cut out.
So draw an outer circle and then draw an inner circle to cut a donut.
To display data, the outer circle can be assembled from arcs whose sweeps indicate your data ( called wedges).
You can draw individual wedges by supplying the starting and ending angles of an arc (in radians).
ctx.arc(cX, cY, radius, startRadians, endRadians, false);
Fill both the canvas and the inner circle with your same gradient to display a consistent gradient.
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/ENZD9/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// define the donut
var cX = Math.floor(canvas.width / 2);
var cY = Math.floor(canvas.height / 2);
var radius = Math.min(cX,cY)*.75;
// the datapoints
var data=[];
data.push(67.34);
data.push(28.60);
data.push(1.78);
data.push(.84);
data.push(.74);
data.push(.70);
// colors to use for each datapoint
var colors=[];
colors.push("teal");
colors.push("rgb(165,42,42)");
colors.push("purple");
colors.push("green");
colors.push("cyan");
colors.push("gold");
// track the accumulated arcs drawn so far
var totalArc=0;
// draw a wedge
function drawWedge2(percent, color) {
// calc size of our wedge in radians
var WedgeInRadians=percent/100*360 *Math.PI/180;
// draw the wedge
ctx.save();
ctx.beginPath();
ctx.moveTo(cX, cY);
ctx.arc(cX, cY, radius, totalArc, totalArc+WedgeInRadians, false);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
// sum the size of all wedges so far
// We will begin our next wedge at this sum
totalArc+=WedgeInRadians;
}
// draw the donut one wedge at a time
function drawDonut(){
for(var i=0;i<data.length;i++){
drawWedge2(data[i],colors[i]);
}
// cut out an inner-circle == donut
ctx.beginPath();
ctx.moveTo(cX,cY);
ctx.fillStyle=gradient;
ctx.arc(cX, cY, radius*.60, 0, 2 * Math.PI, false);
ctx.fill();
}
// draw the background gradient
var gradient = ctx.createLinearGradient(0,0,canvas.width,0);
gradient.addColorStop(0, "#008B8B");
gradient.addColorStop(0.75, "#F5DEB3");
ctx.fillStyle = gradient;
ctx.fillRect(0,0,canvas.width,canvas.height);
// draw the donut
drawDonut();
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=400 height=300></canvas>
</body>
</html>

How to draw circles in HTML5 canvas with text in them

Seemed simple enough to draw circles and text in an HTML5 canvas, but I get non-intuitive behavior. The circles get drawn nice and pretty, then the more circles I draw, the older circles become more and more octagonal shaped. Very strange to me... Also, the text disappears from the old circles and only appears on the last circle drawn. What's the proper way to do this with canvases?
$("#circles_container").on("click", "#circles_canvas", function(event) {
var canvas = document.getElementById('circles_canvas');
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var w = 16;
var x = event.pageX;
var y = Math.floor(event.pageY-$(this).offset().top);
ctx.fillStyle = "rgb(200,0,0)";
ctx.arc(x, y, w/2, 0, 2 * Math.PI, false);
ctx.fill();
ctx = canvas.getContext("2d");
ctx.font = '8pt Calibri';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText('0', x, y+3);
}
});
Just add this near the start of your function :
ctx.beginPath();
You were drawing a path always longer.
Demo in Stack Snippets & JS Fiddle (click on the canvas)
var canvas = document.getElementById('circles_canvas');
canvas.addEventListener("click", function(event) {
if (canvas.getContext) {
var ctx = canvas.getContext("2d");
var w = 16;
var x = Math.floor(event.pageX-this.offsetLeft);
var y = Math.floor(event.pageY-this.offsetTop);
ctx.beginPath();
ctx.fillStyle = "rgb(200,0,0)";
ctx.arc(x, y, w/2, 0, 2 * Math.PI, false);
ctx.fill();
ctx.font = '8pt Calibri';
ctx.fillStyle = 'white';
ctx.textAlign = 'center';
ctx.fillText('0', x, y+3);
}
})
canvas {
border: 1px solid black;
}
<h1>Click Canvas Below</h1>
<canvas id="circles_canvas"></canvas>

Categories