Linear animation on HTML5 Canvas using KineticJS.How to make? - javascript

I am developing an HTML5 canvas based mini-game and I can't seem to organize linear animation.
I am using this code for adding a "target" object to the canvas:
var target = new Kinetic.Shape(function(){
var context = this.getContext();
context.drawImage(images.target, x, y, 2*radius, 2*radius);
context.beginPath();
context.rect(x, y, 2*radius, 2*radius);
context.closePath();
});
gameLayer.add(target);
I need to animate this object with linear animation, trying this code:
var mx = x;
setInterval(function(){
mx -= 1;
target.setPosition(mx, y);
gameLayer.draw();
}, 500);
But, it didn't work! What is wrong?

I don't know why it doesn't work, but I found the way to do as:
var mx = x;
var my = target.y;
target.transitionTo({
x: mx,
y: my,
rotation: 0,
scale: {x: 1, y: 1},
duration: 1, //time to transition in second
});
You can see more details at: http://www.html5canvastutorials.com/kineticjs/html5-canvas-linear-transition-tutorial-with-kineticjs/

They have an Animation function. So you can
var myAnimationName = new Kinetic.Animation(function (frame) {
console.log(frame.time);
target.setX(target.getX() + 1);
if (target.getX() < somePosition) {
target.setX(somePosition);
this.stop();
}
}, layer);
myAnimationName.start();
You can use also frame.time if needed

Related

Lottie Web: play 2 animations in the same canvas at the same time

I am new to Lottie and am wondering how to play 2 animations side by side at the same time in the same canvas element on the Web.
I have followed this example[1] and then the advice given here[2] with regards to transforming the canvas on every frame to position either animation respectively.
What I would like to achieve is this: 2 red balls bouncing side by side on the same canvas. One playing at x = 0 and one playing at x = 100 on the same canvas.
Here is my approach in a CodePen[3]. I extracted the JS for visibility.
const testjson = {...animation...};
const cvs = document.getElementById("canvas");
const ctx = cvs.getContext("2d");
// Drawing sth on the context to see whether sharing works
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 40, 40);
function renderAnimation(canvasContext, translation) {
const animData = {
renderer: "canvas",
loop: true,
rendererSettings: {
context: canvasContext,
clearCanvas: true,
},
animationData: testjson,
};
const anim = bodymovin.loadAnimation(animData);
// Transform the canvas for the respective animation on enter-frame
anim.addEventListener("enterFrame", () => {
ctx.translate(translation.x, translation.y);
});
// If efective, causes onion-effect
// anim.setSubframe(false);
}
renderAnimation(ctx, { x: 0, y: 0 });
renderAnimation(ctx, { x: 100, y: 0 });
Alas, the way I implemented it does not seem to work.
Does anybody know what I am missing?
Thank you!
[1]: https://codepen.io/RenanSgorlom/pen/orgxyJ
[2]: https://github.com/airbnb/lottie-web/issues/1671
[3]: https://codepen.io/user1207504/pen/MWVYvxd
You don't need that much code to have some balls bouncing on the same canvas ...
Certainly no need for a library to just do that.
The balls we can draw using arc functions:
https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/arc
movement is to increase the position on x or y, in the code is the x += vx
bouncing we just change the direction, you can see it in my code vx *= -1
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
class ball {
constructor(data) {
this.data = data
}
draw() {
this.data.x += this.data.vx
this.data.y += this.data.vy
if (this.data.x > canvas.width || this.data.x < 0) this.data.vx *= -1
if (this.data.y > canvas.height || this.data.y < 0) this.data.vy *= -1
ctx.beginPath()
ctx.fillStyle = this.data.color
ctx.arc(this.data.x,this.data.y, this.data.radius, 0, 2*Math.PI);
ctx.fill()
}
}
const balls = [
new ball({x: 10, y: 10, vx: 0, vy: 1, radius: 8, color: "pink" }),
new ball({x: 90, y: 90, vx: 0, vy: -1, radius: 8, color: "red" }),
new ball({x: 5, y: 50, vx: 1, vy: 1.5, radius: 8, color: "cyan" })
]
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height)
balls.forEach(b => b.draw())
requestAnimationFrame(animate)
}
animate()
<canvas id="canvas" width=100 height=100></canvas>

HTML5 Canvas: how i can deal with inverted translate() after rotation?

I need to apply several matrix transformations before drawing a shape, however (if on somewhere) I use rotate() the coordinates are inverted and/or reversed and cannot continue without knowing if the matrix was previously rotated.
How can solve this problem?
Example:
<canvas width="300" height="300"></canvas>
<script>
let canvas = document.querySelector("canvas");
let ctx = canvas.getContext("2d");
ctx.fillStyle = "silver";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.strokeStyle = "black";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, canvas.height/2);
ctx.lineTo(canvas.width, canvas.height/2);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(canvas.width/2, 0);
ctx.lineTo(canvas.width/2, canvas.height);
ctx.stroke();
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
// move the red rectangle 100px to the left (top-left)
// but instead is moved on y axis (right-bottom)
ctx.translate(-100, 0);
// more matrix transformations
// ....
// ....
// now finally draw the shape
ctx.fillStyle = "red";
ctx.fillRect(150, 150, 100, 50);
</script>
Can be this Translation after rotation the solution?
It looks like you aren't resetting the canvas matrix each time you make a new transformation.
The Canvas API has the save() and restore() methods. Canvas states are stored on a stack. Every time the save() method is called, the current drawing state is pushed onto the stack. A drawing state consists of transformations that have been applied along with the attributes of things like the fillStyle. When you call restore(), the previous settings are restored.
// ...
ctx.save(); // save the current canvas state
ctx.translate(150, 150);
ctx.rotate(-90 * 0.017453292519943295);
ctx.translate(-150, -150);
ctx.restore(); // restore the last saved state
// now the rectangle should move the correct direction
ctx.translate(-100, 0);
Check out this link for more information on the save and restore methods.
OK finally, i solved the problem by rotating the translation point before applying it. This function does the trick:
function helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return { x: point.x * c - point.y * s, y: point.x * s + point.y * c};
}
rotating the translation point using the inverted angle I obtain the corrected translation
helperRotatePoint(translation_point, -rotation_angle);
working code:
let canvas = document.querySelector("canvas");
// proper size on HiDPI displays
canvas.style.width = canvas.width;
canvas.style.height = canvas.height;
canvas.width = Math.floor(canvas.width * window.devicePixelRatio);
canvas.height = Math.floor(canvas.height * window.devicePixelRatio);
let ctx = canvas.getContext("2d");
ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
ctx.fillStyle = "whitesmoke";
ctx.fillRect(0, 0, canvas.width, canvas.height);
class UIElement {
constructor(x, y, width, height, color) {
// PoC
this.draw_pos = {x, y};
this.draw_size = {width, height};
this.color = color;
this.rotate = 0;
this.scale = {x: 1, y: 1};
this.translate = {x: 0, y: 0};
this.skew = {x: 0, y: 0};
this.childs = [];
}
addChild(uielement) {
this.childs.push(uielement);
}
helperRotatePoint(point, angle) {
let s = Math.sin(angle);
let c = Math.cos(angle);
return {
x: point.x * c - point.y * s,
y: point.x * s + point.y * c
};
}
draw(cnvs_ctx, parent_x, parent_y) {
// backup current state
cnvs_ctx.save();
let elements_drawn = 1;// "this" UIElement
// step 1: calc absolute coordinates
let absolute_x = parent_x + this.draw_pos.x;
let absolute_y = parent_y + this.draw_pos.y;
// step 2: apply all transforms
if (this.rotate != 0) {
cnvs_ctx.translate(absolute_x, absolute_y)
cnvs_ctx.rotate(this.rotate);
cnvs_ctx.translate(-absolute_x, -absolute_y);
// rotate translate point before continue
let tmp = this.helperRotatePoint(this.translate, -this.rotate);
// apply rotated translate
cnvs_ctx.translate(tmp.x, tmp.y);
} else {
cnvs_ctx.translate(this.translate.x, this.translate.y);
}
cnvs_ctx.scale(this.scale.x, this.scale.y);
cnvs_ctx.transform(1, this.skew.y, this.skew.x, 1, 0, 0);
// step 3: self draw (aka parent element)
cnvs_ctx.fillStyle = this.color;
cnvs_ctx.fillRect(absolute_x, absolute_y, this.draw_size.width, this.draw_size.height);
// step 4: draw childs elements
for (let i = 0; i < this.childs.length ; i++) {
elements_drawn += this.childs[i].draw(
cnvs_ctx, absolute_x, absolute_y
);
}
// done, restore state
cnvs_ctx.restore();
return elements_drawn;
}
}
// spawn some ui elements
var ui_panel = new UIElement(120, 50, 240, 140, "#9b9a9e");
var ui_textlabel = new UIElement(10, 10, 130, 18, "#FFF");
var ui_image = new UIElement(165, 25, 90, 60, "#ea9e22");
var ui_textdesc = new UIElement(17, 46, 117, 56, "#ff2100");
var ui_icon = new UIElement(5, 5, 10, 10, "#800000");
ui_panel.addChild(ui_textlabel);
ui_panel.addChild(ui_image);
ui_panel.addChild(ui_textdesc);
ui_textdesc.addChild(ui_icon);
// add some matrix transformations
ui_textdesc.skew.x = -0.13;
ui_textdesc.translate.x = 13;
ui_image.rotate = -90 * 0.017453292519943295;
ui_image.translate.y = ui_image.draw_size.width;
ui_panel.rotate = 15 * 0.017453292519943295;
ui_panel.translate.x = -84;
ui_panel.translate.y = -50;
// all ui element elements
ui_panel.draw(ctx, 0, 0);
<canvas width="480" height="360"></canvas>

How can I execute different animations one by one within a HTML5 Canvas context

I am working to create an animation which draws a canvas like "O - O" shape.
The animation should first animate to draw the circle on the left, then the right circle and finally the connection in between.
I am able to draw a circle but I would like to know how can I draw the three elements one by one instead of drawing three of them together.
Psuedo-code:
window.onload = draw;
function draw(){
drawcircle();
}
function drawcircle(){
draw part of circle
if( finish drawing){
clearTimeout
}
else{
setTimeout(drawcircle());
}
However, if I run another drawcircle function after first in the draw() function. Both circles are drawn at the same time instead of one by one. Is there any method to draw each elements one by one?
Thanks very much
What you want are callbacks:
Circle(ctx, 50, 50, 25, 1000, function () { // animate drawing a circle
Circle(ctx, 150, 50, 25, 1000, function () { // then animate drawing a circle
Line(ctx, 75, 50, 125, 50, 1000, function(){ // then animate drawing a line
alert('done');
});
});
});
Here's a simple implementation of animated drawings of circles and lines:
function Circle(context, x, y, radius, dur, callback) {
var start = new Date().getTime(),
end = start + dur,
cur = 0;
(function draw() {
var now = new Date().getTime(),
next = 2 * Math.PI * (now-start)/dur;
ctx.beginPath();
ctx.arc(x, y, radius, cur, next);
cur = Math.floor(next*100)/100; // helps to prevent gaps
ctx.stroke();
if (cur < 2 * Math.PI) requestAnimationFrame(draw); // use a shim where applicable
else if (typeof callback === "function") callback();
})();
}
function Line(context, x1, y1, x2, y2, dur, callback) {
var start = new Date().getTime(),
end = start + dur,
dis = Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2)),
ang = Math.atan2(y2-y1, x2-x1),
cur = 0;
(function draw() {
var now = new Date().getTime(),
next = Math.min(dis * (now-start)/dur, dis);
ctx.beginPath();
ctx.moveTo(x1 + Math.cos(ang) * cur, y1 + Math.sin(ang) * cur);
ctx.lineTo(x1 + Math.cos(ang) * next, y1 + Math.sin(ang) * next);
cur = next;
ctx.closePath();
ctx.stroke();
if (cur < dis) requestAnimationFrame(draw); // use a shim where applicable.
else if (typeof callback === "function") callback();
})();
}
And here's a working (webkit only) demo: http://jsfiddle.net/QSAyw/3/
What you propabably really want to be using is requestAnimationFrame. You can then completely leave out the setTimeout. http://paulirish.com/2011/requestanimationframe-for-smart-animating/ is a great blog post that'll help you get started.

Making a circle that moves

Been trying to get something to work in HTML and I haven't been able to nail it down quite yet. Basically, I want to create a canvas and then make a circle inside the canvas that moves from edge of the canvas to edge of the canvas. Any suggestions?
EDIT:
Folks wanted what I have so far, so here it is:
<html>
<head>
<script type="text/javascript">
function draw () {
var canvas = document.getElementById('circle');
if (canvas.getContext) {
var context = canvas.getContext('2d');
context.fillStyle = "rgb(150,29,28)";
var startPoint = (Math.PI/180)*0;
var endPoint = (Math.PI/180)*360;
context.beginPath();
context.arc(200,200,150,startPoint,endPoint,true);
context.fill();
context.closePath();
}
}
}
</script>
</head>
<body onload="init();">
<canvas id="canvas" width="500" height="500"></canvas><br>
</body>
</html>
I'm not really sure how to get a circle onto the canvas (and I'm still shakey on the implementation thereof) as well as how to make it, y'know, move. I have examples of how to rotate something, but not really how to move it. Sorry for the inexperience guys, trying to learn HTML on my own, but the book I've got doesn't seem really descriptive on this aspect, even though it's a supposed to be teaching me HTML.
So up to now you have a code where you're able to draw a circle at a certain position onto the canvas surface, very well, now in order to make it look like it's moving you'll have to keep drawing it again and again with it's position slightly changed to give a smooth motion effect to it, it's a standard to draw things 60 times per second (I think that's because 60 frames per second is the most that the human eye can notice, or whatever). And of course, each time you draw it on another position, it's going to be necessary to clear the older drawing.
Let's change your code just a bit in order to make it animation-friendly:
<script type="text/javascript">
function init()
{
canvas = document.getElementById('canvas');
if(canvas.getContext)
context = canvas.getContext('2d');
else return;
setInterval(draw, 1000 / 60); // 60 times per second
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
// var startPoint = (Math.PI/180)*0; Kinda redundant, it's just 0
// var endPoint = (Math.PI/180)*360; Again, it's just PI times 2
context.beginPath();
context.arc(200, 200, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
</script>
</head>
<body onload="init();">
<canvas id="canvas" width="500" height="500"></canvas><br>
Now there are lots of funny ways to make an object move towards a fixed point, but the simplest is, of course, moving along a straight line. To do so you'll need
A vector containing the object's current position
A vector containing the object's target position
Let's change your code so we have those at hand
var canvas, context,
position = {x: 200, y: 200},
target = {x: 400, y: 400};
function init()
{
...
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
context.beginPath();
context.arc(position.x, position.y, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
}
Good! This way whenever you change one of the values in the position object, the position of your circle is going to be affected.
If you subtract the current position from the target position you'll get another vector which points straight to the target position coming from the current position right? So get that vector and just normalize it, turning it's length to 1, the result will actually be the (cosine, sine) of the angle from the target to the object's position. What that means is if you add that normalized vector to the object's current position, the object will move towards the target position 1 unit at a time.
Normlizing a vector is simply dividing it's components by it's length.
function normalize(v)
{
var length = Math.sqrt(v.x * v.x + v.y * v.y);
return {x: v.x / length, y: v.y / length};
}
var step = normalize({x: target.x - position.x, y: target.y - position.y});
Ok, now all we need to do is keep adding the step vector to the object's current position until it reaches the target position.
function normalize(v)
{
var length = Math.sqrt(v.x * v.x + v.y * v.y);
return {x: v.x / length, y: v.y / length};
}
var canvas, context,
position = {x: 200, y: 200},
target = {x: 400, y: 400},
step = normalize({x: target.x - position.x, y: target.y - position.y});
function init()
{
canvas = document.getElementById('canvas');
if(canvas.getContext)
context = canvas.getContext('2d');
else return;
setInterval(draw, 1000 / 60);
}
function draw()
{
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillStyle = "rgb(150,29,28)";
context.beginPath();
context.arc(position.x, position.y, 150, 0, Math.PI * 2, true);
context.fill();
context.closePath();
position.x += step.x;
position.y += step.y;
}
And there you have it. Of course it's pretty basic code, you'll have to add a codition to check whether or not the object has arrived to the target otherwise it will just keep going past the target. If it's moving too slow, just scale the step vector by an arbitrary speed factor. Also in a real world app you'd have lot's of objects and not only just a circle so you'd have to make it entirely object-oriented since every object would have it's position, target position, color etc etc etc. A library to work with vectors would come in handy. This is one I wrote for myself some time ago: http://pastebin.com/Hdxg8dxn
Here you go, give this a crack -
<!DOCTYPE html>
<html>
<head>
<script>
function byId(e){return document.getElementById(e);}
function newEl(tag){return document.createElement(tag);}
function newTxt(txt){return document.createTextNode(txt);}
function toggleClass(element, newStr)
{
index=element.className.indexOf(newStr);
if ( index == -1)
element.className += ' '+newStr;
else
{
if (index != 0)
newStr = ' '+newStr;
element.className = element.className.replace(newStr, '');
}
}
function forEachNode(nodeList, func)
{
var i, n = nodeList.length;
for (i=0; i<n; i++)
{
func(nodeList[i], i, nodeList);
}
}
window.addEventListener('load', mInit, false);
var canvas, hdc;
var posx=50, posy=0, radius=50;
function circle(hdc, x, y, radius)
{
hdc.beginPath();
hdc.arc(x, y, radius, 0, 2*Math.PI);
hdc.stroke();
//arc(x,y,r,start,stop)
}
function mInit()
{
canvas = byId('tgtCanvas');
hdc = canvas.getContext('2d');
//circle(hdc, posx, posy, 50);
setInterval(animateStep, 50);
}
var velX = 2;
function animateStep()
{
posx += velX;
if (posx+radius > canvas.width)
velX *= -1;
else if (posx-radius < 0)
velX *= -1;
hdc.clearRect(0,0,canvas.width,canvas.height);
circle(hdc, posx, posy, radius);
}
</script>
<style>
</style>
</head>
<body>
<canvas id='tgtCanvas' width='256' height='256'></canvas>
</body>
</html>

raphael viewbox animated zoom

I'm build a kind of javascript map with javascript and the Raphael lib.
I'm able to zoom on an object when clicked, but I want it to be animated (like slowly diving in and so on). Is there a way to do so without reinventing the wheel?
There is no reason that the viewbox of an svg object cannot be animated -- Raphael simply doesn't provide such functionality out of the box. Creating a plugin is reasonably straightforward, however. For instance:
Raphael.fn.animateViewBox = function animateViewBox( x, y, w, h, duration, easing_function, callback )
{
var cx = this._viewBox ? this._viewBox[0] : 0,
dx = x - cx,
cy = this._viewBox ? this._viewBox[1] : 0,
dy = y - cy,
cw = this._viewBox ? this._viewBox[2] : this.width,
dw = w - cw,
ch = this._viewBox ? this._viewBox[3] : this.height,
dh = h - ch,
self = this;;
easing_function = easing_function || "linear";
var interval = 25;
var steps = duration / interval;
var current_step = 0;
var easing_formula = Raphael.easing_formulas[easing_function];
var intervalID = setInterval( function()
{
var ratio = current_step / steps;
self.setViewBox( cx + dx * easing_formula( ratio ),
cy + dy * easing_formula( ratio ),
cw + dw * easing_formula( ratio ),
ch + dh * easing_formula( ratio ), false );
if ( current_step++ >= steps )
{
clearInterval( intervalID );
callback && callback();
}
}, interval );
}
Any paper instantiated after this plugin is installed can use animateViewBox in exactly the same method Raphael's built-in animate method works. For instance...
var paper = Raphael( 0, 0, 640, 480 );
paper.animateViewBox( 100, 100, 320, 240, 5000, '<>', function()
{
alert("View box updated! What's next?" );
} );
Demonstration staged here.
Raphael animations work by animating element attributes. When you call element.animate, you provide the final object parameters, the time it takes to get there and possibly an easing function if you don't want it to be linear.
For example, to scale up/down an circle you might consider this example: http://jsfiddle.net/eUfCg/
// Creates canvas 320 × 200 at 10, 50
var paper = Raphael(10, 50, 320, 200);
// Creates circle at x = 50, y = 40, with radius 10
var circle = paper.circle(50, 40, 10);
// Sets the fill attribute of the circle to red (#f00)
circle.attr("fill", "#f00");
// Sets the stroke attribute of the circle to white
circle.attr("stroke", "#fff");
var zoomed = false;
circle.click(function () {
if (zoomed) {
this.animate({ transform: "s1" }, 500);
zoomed = false;
} else {
this.animate({ transform: "s4" }, 500);
zoomed = true;
}
});​
Which animates the transform property of the circle. To scale your map you should put all of the elements inside a group, and animate the transform property of the group, considering the scale and translation that you want to end up with.
See http://raphaeljs.com/reference.html#Element.transform for more information on the transform property.

Categories