I have this code to show wind direction on html canvas:
var x, y, r, ctx, radians;
ctx = window.compass.getContext("2d");
radians = 0.0174533 * (10 - 90);
x = ctx.canvas.width / 2;
y = ctx.canvas.height / 2;
r = x * 0.8;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height );
ctx.beginPath();
ctx.strokeStyle = "red"
ctx.fillStyle = "rgba(255,0,0,0.5)";
ctx.lineCap = 'square';
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.lineWidth = 10;
ctx.moveTo(x, y);
ctx.lineTo(x + r * Math.cos(radians), y + r * Math.sin(radians));
ctx.stroke();
I want to add arrowhead to the start point of the line, i.e
x = ctx.canvas.width / 2;
y = ctx.canvas.height / 2;
how can I do this?
Creating a reusable wind-arrow is fairly straightforward:
Create your shadowed arrow on an in-memory canvas:
var memCanvas=document.createElement('canvas')
Draw the in-memory canvas onto the visible canvas:
ctx.drawImage(memCanvas,x,y);
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;
var cx=150;
var cy=150;
var angle=0;
var arrow=makeArrow(100,10,20,20,'red',4);
requestAnimationFrame(animate);
function animate(time){
ctx.clearRect(0,0,cw,ch);
drawArrow(cx,cy,angle);
angle+=Math.PI/180;
requestAnimationFrame(animate);
}
function drawArrow(cx,cy,radianAngle){
ctx.translate(cx,cy);
ctx.rotate(radianAngle);
ctx.drawImage(arrow,-arrow.width/2,-arrow.height/2);
ctx.rotate(-radianAngle);
ctx.translate(-cx,-cy);
}
function makeArrow(length,lineHeight,arrowLength,arrowHeight,fillcolor,shadowoffset){
var lineTop=(arrowHeight-lineHeight)/2;
var arrowLeft=length-arrowLength;
var c=document.createElement('canvas');
var ctx=c.getContext('2d');
c.width=length+shadowoffset;
c.height=arrowHeight+shadowoffset;
ctx.shadowOffsetX = shadowoffset;
ctx.shadowOffsetY = shadowoffset;
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
ctx.beginPath();
ctx.moveTo(0,lineTop);
ctx.lineTo(arrowLeft,lineTop);
ctx.lineTo(arrowLeft,0);
ctx.lineTo(length,arrowHeight/2);
ctx.lineTo(arrowLeft,arrowHeight);
ctx.lineTo(arrowLeft,lineTop+lineHeight);
ctx.lineTo(0,lineTop+lineHeight);
ctx.closePath();
ctx.fillStyle=fillcolor;
ctx.fill();
return(c);
}
body{ background-color:white; padding:10px; }
canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>
Basically I want to draw a slime from Dragon Quest (it looks like a water drop). I have done the following, but I'd like to know if there is a better way to do it. Maybe using Bezier curves or something like that?
Update:
I actually got everything to look like how I want it, except for the gradient for the glow spots (white)
Slime.prototype.drawEye = function(x,y,w,h)
{
ctx.save();
ctx.lineWidth = 3;
ctx.beginPath();
var c = [];
c.push("sclera");
//c.push("iris");
c.push("pupil");
var wScale = [1,2.2,3];
var hScale = [1,2,1.3];
for (var i = 0; i < c.length; i++)
{
w /= wScale[i];
h /= hScale[i];
ctx.beginPath();
ctx.ellipse(x,y,w,h,0,degreesToRadians(360),false);
ctx.fillStyle = this.colors[c[i]];
ctx.fill();
ctx.stroke();
}
ctx.restore();
}
Slime.prototype.drawMouth = function()
{
ctx.save();
ctx.beginPath();
var x = this.x+this.w/30;
var y = this.y+this.h/8;
var w = this.w;
var h = this.h/8;
var start = [x-w/3,y];
var end = [x+w/8,y+h/5];
// mouth
bezierCurve(start,[x-w/3.3,y+h*2.3],[x+w/8,y+h*1.8],end);
bezierCurveTo([x-w/10,y+h*0.6],[x-w/6,y+h*0.6],start);
ctx.lineCap = "round";
ctx.lineWidth = 8;
ctx.stroke();
ctx.clip();
ctx.fillStyle = this.colors["mouth"];
ctx.fill();
start = [x-w/3.5,y+h*1];
// tongue
ctx.beginPath();
bezierCurve(start,[x-w/5,y+h*2.5],[x+w/6.5,y+h*1.6],[x+w/9,y+h*0.75]);
bezierCurveTo([x+w/10,y+h/4],[x-w/5,y],start);
ctx.fillStyle = this.colors["tongue"];
ctx.fill();
//ctx.stroke();
ctx.restore();
}
Slime.prototype.drawHealthBar = function()
{
ctx.beginPath();
ctx.rect(this.x-this.w,this.y+this.h/2+30,this.w*2*this.health/this.maxHealth,20);
ctx.fillStyle = "#00ff00";
ctx.fill();
ctx.beginPath();
ctx.rect(this.x-this.w+(this.w*2*this.health/this.maxHealth),this.y+this.h/2+30,(this.maxHealth-this.health)/this.maxHealth*this.w*2,20);
ctx.fillStyle = "#ff0000";
ctx.fill();
ctx.beginPath();
ctx.rect(this.x-this.w,this.y+this.h/2+30,this.w*2,20);
ctx.strokeStyle = "#000000";
ctx.stroke();
}
Slime.prototype.drawBody = function()
{
ctx.save();
ctx.fillStyle = this.colors["body"];
ctx.beginPath();
var x = this.x;
var y = this.y+this.h*0.4;
var w = this.w/2;
var h = this.h;
var left = [x-w*0.97,this.y+h/11];
var bottom = [x,this.y+h*0.495];
var right = [x+w*0.97,this.y+h/15];
var top = [x+this.w/30,this.y-h/2+this.topOffset];
bezierCurve(left,[x-w,y+h/10],[x-w/7,y+h/12],bottom);
bezierCurveTo([x+w*0.8,y+h/10],[x+w*1.05,y-h/7],right);
y = this.y - (y - this.y);
bezierCurveTo([x+w*0.75,y+h/15],[x+w*0.2,y+h/4.5],top);
bezierCurveTo([x-w*0.2,y+h/5],[x-w*0.8,y+h/10],left);
ctx.lineWidth = 3;
ctx.fill();
ctx.strokeStyle = this.colors["bodyOutline"];
ctx.stroke();
ctx.clip();
ctx.shadowColor = "#000033";
ctx.shadowBlur = 40;
ctx.shadowOffsetX = ctx.shadowBlur/4;
ctx.shadowOffsetY = ctx.shadowOffsetX*-0.8;
ctx.lineWidth = 4;
for (var i = 0; i < 7; i++)
{
if (i%2==0)
{
ctx.shadowColor = "#000033";
}
else
{
ctx.shadowColor = "rgba(0,0,80,0.5)";
}
ctx.stroke();
}
ctx.restore();
}
Slime.prototype.drawGlow = function(x,y,r1,r2)
{
ctx.save();
var g = ctx.createRadialGradient(x,y,r1,x,y,r2);
g.addColorStop(0.0,"#ffffff");
g.addColorStop(1.0,this.colors["body"]);
ctx.beginPath();
ctx.arc(x,y,r2,0,degreesToRadians(360),false);
ctx.fillStyle = g;
ctx.fill();
ctx.restore();
}
Slime.prototype.draw = function()
{
ctx.lineWidth = 2;
this.drawBody();
this.drawMouth();
var w = this.w/10;
var h = this.h/10;
var x = this.x;
var y = this.y;
this.drawEye(x-this.w/4.5,y-h/2.5,w,h);
this.drawEye(this.x+this.w/13,y-h/10,w,h);
this.drawGlow(this.x-this.w/2.5,this.y+this.h/12,this.w/50,this.w/16);
this.drawGlow(this.x+this.w/2*0.7,this.y+this.h/10,this.w/50,this.w/16);
}
End update
Yes, you can draw the droplet outline using just 4 cubic Bezier curves.
Here's a proof-of-concept using just 4 C-Bez curves:
Why! Why! Why! .......... Did I enjoy creating a Slime Man??
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
drawDropletMan(20,20,'rgb(63,187,255)');
drawDropletMan(255,55,'rgb(255,180,155)');
function drawDropletMan(x,y,fill){
ctx.translate(x,y);
drawBody(fill);
circularGlow(158,111,3,18,fill);
circularGlow(32,107,3,18,fill);
eye(65,87,17,10);
eye(120,90,17,10);
mouth(33,130);
ctx.translate(-x,-y);
}
//
function drawBody(fillcolor){
ctx.save();
ctx.beginPath();
ctx.moveTo(109,0);
ctx.bezierCurveTo(111,64,199,35,198,123);
ctx.bezierCurveTo(199,139,183,192,99,190);
ctx.bezierCurveTo(66,193,-4,167,1,110);
ctx.bezierCurveTo(9,47,97,45,109,1);
ctx.fillStyle=fillcolor;
ctx.fill();
ctx.strokeStyle='black';
ctx.lineWidth=2;
ctx.stroke();
ctx.clip();
ctx.shadowColor='black';
ctx.shadowBlur=15;
ctx.lineWidth=1;
for(var i=0;i<8;i++){ctx.stroke();}
ctx.restore();
}
//
function circularGlow(x,y,r1,r2,fillcolor){
var g=ctx.createRadialGradient(x,y,r1,x,y,r2);
g.addColorStop(0.00,'white');
g.addColorStop(1.00,fillcolor);
ctx.beginPath();
ctx.arc(x,y,r2,0,Math.PI*2);
ctx.fillStyle=g;
ctx.fill();
}
//
function eye(x,y,r1,r2){
ctx.beginPath();
ctx.arc(x,y,r1,0,Math.PI*2);
ctx.fillStyle='white';
ctx.strokeStyle='black';
ctx.fill();
ctx.lineWidth=2;
ctx.stroke();
//
ctx.beginPath();
ctx.arc(x,y,r2,0,Math.PI*2);
ctx.fillStyle='black';
ctx.fill();
}
//
function mouth(){
ctx.save();
ctx.translate(5,5);
ctx.beginPath();
ctx.moveTo(44,120);
ctx.bezierCurveTo(56,136,112,132,128,123);
ctx.bezierCurveTo(138,123,143,123,132,138);
ctx.bezierCurveTo(113,165,49,169,39,127);
ctx.bezierCurveTo(41,128,32,122,44,120);
ctx.closePath();
ctx.fillStyle='black';
ctx.fill();
ctx.clip();
ctx.beginPath();
ctx.arc(90,200,56,0,Math.PI*2);
ctx.fillStyle='coral';
ctx.fill();
ctx.restore();
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=500 height=300></canvas>
I am attempting to animate an emoticon that was previously drawn in canvas. I am attempting to do a draw and clear using frames following a tutorial but am not getting results. I have 6 frames of the emoticon coded and am unsure how to include this within the code. This is what I have so far:
<!DOCTYPE html>
<html>
<head>
<title>Adding Animation</title>
<style>
canvas {
border: 3px #CCC solid;
}
</style>
</head>
<body>
<div id="container">
<canvas id="myCanvas" height="1200" width="900"></canvas>
</div>
<script>
var mainCanvas = document.querySelector("#myCanvas");
var mainContext = mainCanvas.getContext("2d");
var canvasWidth = mainCanvas.width;
var canvasHeight = mainCanvas.height;
function drawCircle() {
mainContext.clearRect(0, 0, canvasWidth, canvasHeight);
// color in the background
mainContext.fillStyle = "#EEEEEE";
mainContext.fillRect(0, 0, canvasWidth, canvasHeight);
// draw the circle
ctx.beginPath();
ctx.strokeStyle = "000000";
ctx.lineWidth = 5;
ctx.fillStyle = "yellow";
ctx.arc(600, 450, 150, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.fill();
//The smile
ctx.beginPath();
ctxstrokeStyle = "black";
ctx.lineWidth = 2;
ctx.arc(600, 475, 75, .1 * Math.PI, Math.PI * .9, false)
ctx.stroke();
ctx.closePath();
//The eyes
//Left
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(850, 405, 40, 0 * Math.PI, Math.PI * 2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
//Right
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(1000,405,40, 0*Math.PI, Math.PI*2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore()
}
drawCircle();
</script>
</body>
</html>
I am unsure if I am even on the right track as I have a difficult time with animation. Does anyone have any suggestions they can give me guidance on?
You have 2 names for the context: mainContext & ctx.
Change it to a single name and your face is "smiley" ! :-)
...
To animate:
Use a requestAnimationFrame loop to change the scaleY value in scale(scaleX,scaleY) over time.
Here's annotated code and a Demo:
var mainCanvas = document.querySelector("#myCanvas");
var ctx = mainCanvas.getContext("2d");
var canvasWidth = mainCanvas.width;
var canvasHeight = mainCanvas.height;
ctx.translate(-425,-275);
drawCircle(1);
// global var to hold pct the left eye is open
// 1==fully open, 0==fully closed
var scaley=1;
var direction=-1;
// request 1 animate() loop
requestAnimationFrame(animate);
function animate(time){
// draw smiley with the specified eye openness
drawCircle(scaley);
scaley+=.02*direction;
if(scaley<0){
scaley=0;
direction=1;
}
if(scaley>1){
scaley=1;
direction=-1;
}
requestAnimationFrame(animate);
}
function drawCircle(scaleY) {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// color in the background
ctx.fillStyle = "#EEEEEE";
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// draw the circle
ctx.beginPath();
ctx.strokeStyle = "000000";
ctx.lineWidth = 5;
ctx.fillStyle = "yellow";
ctx.arc(600, 450, 150, 0, Math.PI * 2, true);
ctx.stroke();
ctx.closePath();
ctx.fill();
//The smile
ctx.beginPath();
ctxstrokeStyle = "black";
ctx.lineWidth = 2;
ctx.arc(600, 475, 75, .1 * Math.PI, Math.PI * .9, false)
ctx.stroke();
//The eyes
//Left
ctx.save();
// move the [0,0] origin to the left eye's centerpoint
ctx.translate(550,405);
// close the left eye by the specified scaleY
ctx.scale(0.65, scaleY);
ctx.beginPath();
// draw the left eye (arc) at 0,0 because
// we translated the origin to [550,405] earlier
ctx.arc(0, 0, 40, 0 * Math.PI, Math.PI * 2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore();
//Right
ctx.save();
ctx.scale(0.65, 1);
ctx.beginPath();
ctx.arc(1000,405,40, 0*Math.PI, Math.PI*2, false);
ctx.fillStyle="black";
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.restore()
}
<canvas id="myCanvas" height="1200" width="900"></canvas>
You never declared a ctx variable.
Change all your mainContext by ctx and it should be working fine.
I want to draw one circle and a character with shadow on a canvas in a HTML page while loading the page and recreate the image on a button click. I am using this code:
window.onload = function() {
draw();
};
function draw(){
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
var width = c.width;
var height = c.height;
//DRAW A CIRCLE
var centerX = Math.floor((Math.random() * width));
var centerY = Math.floor((Math.random() * height));
var radius = Math.floor(Math.random() * 50);
var color = '#f11';
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
//DRAW A CHARACTER WITH SHADOW
var c = "S";
ctx.font = "300% Verdana";
ctx.shadowBlur = 20;
ctx.shadowColor = "black";
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 20;
ctx.fillStyle = "#111";
ctx.fillText(c, 10, 90);
}
In HTML I am calling draw function onclick() event of a button named Refresh.
For the first time it is giving desired output by drawing one circle and a character with shadow. As I click on the Refresh button it is drawing both the objects with shadow. I dont want to draw shadow of the circle. Can anyone please tell me the mistake I'm doing here.
You may want to use the CanvasRenderingContext2D.save() method :
window.onload = function() {
draw();
};
document.getElementById("canvas").addEventListener('click', draw);
function draw(){
var c = document.getElementById("canvas");
var ctx = c.getContext("2d");
ctx.clearRect(0, 0, c.width, c.height);
var width = c.width;
var height = c.height;
//DRAW A CIRCLE
var centerX = Math.floor((Math.random() * width));
var centerY = Math.floor((Math.random() * height));
var radius = Math.floor(Math.random() * 50);
var color = '#f11';
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(centerX, centerY, radius, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
//DRAW A CHARACTER WITH SHADOW
//save the actual context
ctx.save();
var c = "S";
ctx.font = "300% Verdana";
ctx.shadowBlur = 20;
ctx.shadowColor = "black";
ctx.shadowOffsetX = 20;
ctx.shadowOffsetY = 20;
ctx.fillStyle = "#111";
ctx.fillText(c, 10, 90);
//restore it
ctx.restore();
}
canvas{border:1px solid;}
<canvas id="canvas" width="400" height="200"></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>