Julia Set Viewer - javascript

I've been trying to make a julia set viewer over at my site http://thejamespaterson.com/scripts/julia/, but I'm currently having trouble getting the program to display the correct julia set. For example, when testing with C value 0+0i, I get the following image:
The result is supposed to be a circle. I'm not sure why this is happening. I wrote my own complex numbers library and plotting functions, and they are posted below. Any help would be appreciated;
function complexNum(real, imaginary) {
this.real = real;
this.imaginary = imaginary;
return this;
}
function addComplex(c1, c2) {
this.real = c1.real + c2.real;
this.imaginary = c1.imaginary + c2.imaginary;
return this;
}
function multComplex(c1, c2) {
this.real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
this.imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
return this;
}
function dispComplex(c) {
var sign = '';
if (c.imaginary >= 0) {
sign = '+';
}
return c.real + sign + c.imaginary + "i";
}
function getComplexModulus(c) {
return Math.sqrt((c.real * c.real) + (c.imaginary * c.imaginary));
}
//globals
var MAXITERATION = 100;
var BOUNDARY = 4;
var CANVASID = "juliaDraw";
var CONTEXT = document.getElementById("juliaDraw").getContext('2d');
var HEIGHT = 750;
var WIDTH = 750;
var juliaImageData = CONTEXT.createImageData(WIDTH, HEIGHT);
function readInput(inputID) {
return document.getElementById(inputID).value;
}
function drawPointOnCanvas(x, y, color) {
//console.log('drawing pixel at '+x+','+y);
CONTEXT.fillStyle = color;
CONTEXT.fillRect(x, y, 1, 1);
}
function createArray(length) {
var arr = new Array(length || 0),
i = length;
if (arguments.length > 1) {
var args = Array.prototype.slice.call(arguments, 1);
while (i--) arr[length - 1 - i] = createArray.apply(this, args);
}
return arr;
}
function doesPointEscape(c, complexNum) {
var iterations = 0;
var escaped = false;
while ((!escaped) && (iterations < MAXITERATION)) {
if (getComplexModulus(complexNum) > BOUNDARY) {
escaped = true;
}
complexNum = addComplex(multComplex(complexNum, complexNum), c);
iterations++;
}
if (escaped) {
return true;
} else {
return false;
}
}
function plotJuliaSet(canvasID, width, height, c, start, stepsize) {
var complexNumberArray = createArray(width + 1, height + 1);
var doesPointEscapeArray = createArray(width + 1, height + 1);
var real = start.real;
var imaginary = start.imaginary;
console.log('====Drawing Set====');
console.log('c = ' + dispComplex(c));
for (var x = 0; x <= width; x++) {
imaginary = start.imaginary;
for (var y = 0; y <= height; y++) {
complexNumberArray[x][y] = new complexNum(real, imaginary);
doesPointEscapeArray[x][y] = doesPointEscape(c, complexNumberArray[x][y]);
if (doesPointEscapeArray[x][y]) {
//drawPointOnCanvas(x, y,'blue');
} else {
drawPointOnCanvas(x, y, 'black');
//console.log('point '+dispComplex(complexNumberArray[x][y])+' does not escape');
}
imaginary = imaginary - stepsize;
}
real = real + stepsize;
}
//CONTEXT.putImageData(juliaImageData, 0, 0);
console.log('done');
}
function defaultDraw() {
CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
var start = new complexNum(-2, 2);
var c = new complexNum(0, 0);
plotJuliaSet(CANVASID, WIDTH, HEIGHT, c, start, 2 / 350);
}
function drawJulia() {
CONTEXT.clearRect(0, 0, WIDTH, HEIGHT);
var start = new complexNum(-2, 2);
var c = new complexNum(readInput('realValue') * 1, readInput('imagValue') * 1);
plotJuliaSet(CANVASID, WIDTH, HEIGHT, c, start, 2 / 350);
}
<!doctype html>
<html>
<head>
<title>Julia Set Viewer</title>
<style>
.desc {
float: right;
width: 300px;
}
#juliaDraw {
border: 1px dotted;
float: left;
}
</style>
</head>
<body>
<div class="desc">
<h1>Julia Set Viewer</h1>
<p>You can view Julia sets with this simple online tool. Don't know what a Julia set is? Learn about it here.
This script uses a complex number library that I built to handle the arithmetic required to process these images. The source code is hosted on my github.
</p>
</div>
<canvas id="juliaDraw" width=750 height=750 onClick="defaultDraw()"></canvas>
<div class="controls">
<form>
<label>Real:
<input type="text" id="realValue" value="0">
</label>
<label>Imag:
<input type="text" id="imagValue" value="0">
</label>
<input type="button" onClick="drawJulia()">
</form>
</div>
<script src="complex.js"></script>
<script src="juliaset.js"></script>
</body>
</html>

The problem stems from confusion over the use of the this pointer in Javascript.
Change your Julia calculation in doesPointEscape() to
complexNum = new addComplex(new multComplex(complexNum, complexNum), c);
and it works.
This will return a new complex number from multComplex, then add it to c and return a new complex number from addComplex that is assigned to complexNum.
Your multComplex and addComplex functions use the this pointer, but for the this pointer to be referring to one of your complex numbers, you would have to be calling the function on an existing one, or calling new to create a new one.
Alternatively, you could rewrite your multComplex() and addComplex() functions as
function multComplex(c1, c2) {
var real = (c1.real * c2.real) - (c1.imaginary * c2.imaginary);
var imaginary = (c1.real * c2.imaginary) + (c2.real * c1.imaginary);
return new ComplexNum(real, imaginary);
}
function addComplex(c1, c2) {
var real = c1.real + c2.real;
var imaginary = c1.imaginary + c2.imaginary;
return new ComplexNum(real, imaginary);
}
then your doesPointEscape() function should work as-is.

Related

Canvas: Drag and Drop Images

so I am relatively new to javascript. As a school project, we have to code a game with HTML, CSS, and Javascript.
So I want to make a cheap recreate of the game Sort or 'Splode' from New Super Mario Bros Ds.
I already programmed the random spawning of the bombs and animated the movement of the bombs. If they are too long in the middle sector (where they spawn) the explode.
But I didn't program the explosion yet. I want to program the drag and drop first. I just want to drag and drop a bomb. As simple as that, the rest I could do myself I guess.
I searched for a tutorial and found this webpage: https://html5.litten.com/how-to-drag-and-drop-on-an-html5-canvas/
I sort of recreated this in my game, but it is not working...
Sorry for my bad code. Could someone please explain to me how to do that?
Best,
Sebastian
enter image description here
<!DOCTYPE html>
<html>
<head>
<title>BOMB-Sorter</title>
<style>
canvas {
background-color:#fff;
border:7px solid #000;
border-radius: 5px;
display: inline;
background-image: url("img/background.jpg");
background-size: cover;
}
body{
display: flex;
justify-content: center;
align-items: center;
}
</style>
<script>
class GraphObj {
constructor(x, y, w, h, c, x_vel, y_vel, t, d) {
this.x = x;
this.y = y;
this.w = w;
this.h = h;
this.c = c;
this.x_vel = x_vel;
this.y_vel = y_vel;
this.t = t;
this.d = d;
}
info() {
return "\ngraphical object: x="+this.x + ", y="+this.y+", w="+this.w+", h="+this.h+", h="+this.c;
}
}
// Declaration of Lists
var bList = null; // List of Bombs
var xlist = null;
var ylist = null;
var ref_time=0; // Reference Time
var lives = 10;
var score = 0;
var dragok = false;
var c = document.getElementById("myCanvas");
function drawGrid(ele,ctx){ // declaration of a new function
ctx.globalAlpha = 0.5;
ctx.fillStyle = "#ff9999";
ctx.fillRect(0,0,ele.width/3,ele.height)
ctx.fillStyle = "#60615a";
ctx.fillRect(2* (ele.width/3),0,ele.width/3,ele.height)
ctx.globalAlpha = 1;
ctx.setLineDash([25, 15]);
ctx.moveTo(ele.width/3 +1,0);
ctx.lineTo(ele.width/3 +1, ele.height);
ctx.moveTo(2*(ele.width/3 -1),0);
ctx.lineTo(2*(ele.width/3 - 1), ele.height);
ctx.lineWidth = 10;
ctx.strokeStyle = "#f5f242";
ctx.stroke();
}
function drawAll()
{
var ele = document.getElementById("myCanvas");
var ctx = ele.getContext("2d");
ele.width = ele.width; // clear everything!
drawGrid(ele,ctx); // call the function ...
var bombb = document.getElementById("bombb");
var bombr = document.getElementById("bombr");
var bombr_step = document.getElementById("bombr_step");
var bombb_step = document.getElementById("bombb_step");
var bombw = document.getElementById("bombw");
// draw bombs
for (var i=0; i<bList.length; i++)
{
var o = bList[i];
if(o.c == 0 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 1 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 2 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombr_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.c == 3 && o.t != 8 && o.t != 10 ){
ctx.drawImage(bombb_step, o.x,o.y,o.w,o.h);
draggable: true;
}
if(o.t == 10 || o.t == 8){
ctx.drawImage(bombw, o.x,o.y,o.w,o.h);
draggable: true;
}
}
// draw lives
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "red";
ctx.fillText(+lives, ele.width - 105, 70);
// draw score
ctx.font = "normal small-caps 70px Minecraft";
ctx.fillStyle = "black";
ctx.fillText(+score, 20, 70);
}
function positionCheck(i, ele){
if(bList[i].x < ele.width/3){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].x > 2*(ele.width/3) -64){
bList[i].x_vel = bList[i].x_vel * (-1);
}
else if(bList[i].y < 0){
bList[i].y_vel = bList[i].y_vel * (-1);
}
else if(bList[i].y > ele.height -64){
bList[i].y_vel = bList[i].y_vel * (-1);
}
}
function animateStep(){
for(var i = 0;i < bList.length; i++){
if(bList[i].c == 0){
bList[i].c = 2;
bList[i].t++;
}
else if(bList[i].c == 1){
bList[i].c = 3;
bList[i].t++;
}
else if(bList[i].c == 2){
bList[i].c = 0;
}
else if(bList[i].c == 3){
bList[i].c = 1;
}
else if(bList[i].t == 8){
bList[i].c = 4;
}
else if(bList[i].t == 10){
bList[i].c = 4;
}
}
}
function functimer(){
var ele = document.getElementById("myCanvas");
// create new bomb every s
if (Date.now() - ref_time >= 1000)
{
// random number between 0 and ele.width
var pos_x = Math.floor(Math.random() * (ele.width/3 - 64)) + (ele.width/3 +1);
//random height
var pos_y = Math.floor(Math.random() * (ele.height - 70) -1);
//random color
var color = Math.round(Math.random());
//random x velocity
var x_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
//random y velocity
var y_vel = Math.ceil(Math.random() * 2) * (Math.round(Math.random()) ? 1 : -1);
var t = 0;
var o = new GraphObj(pos_x,pos_y,64,64, color, x_vel, y_vel, t); // create new bomb
bList.push(o);
animateStep();
ref_time = Date.now();
}
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
// move bombs
for (var i=0; i<bList.length; i++)
{
bList[i].y += bList[i].y_vel;
bList[i].x += bList[i].x_vel;
positionCheck(i, ele);
}
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
function myMove(i,e){
if(dragok == true){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
}
}
function myDown(e){
for(var i = 0; i < bList.lenght; i++){
if(e.pageX < bList[i].x + 64 + c.offsetLeft && e.pageX > bList[i].x + c.offsetLeft && e.pageY < bList[i].y + 64 + c.offsetTop && e.pageY > bList[i].y + c.offsetTop){
bList[i].x = e.pageX - c.offsetLeft;
bList[i].y = e.pageY - c.offsetTop;
bList[i].x_vele = 0;
bList[i].y_vel = 0;
dragok = true;
c.onmousemove = myMove(i,e);
}
}
}
function myUp(){
dragok = false;
}
function start() {
// create lists
bList = new Array();
var c = document.getElementById("myCanvas");
c.setAttribute('tabindex','0');
c.focus();
c.onmousedown = myDown;
c.onmouseup = myUp;
var xlist = null;
xlist = new Array();
var ylist = null;
ylist = new Array();
drawAll();
requestAnimationFrame(functimer); // draw everything BEFORE screen update
}
</script>
</head>
<body onload="start()">
<img id="bombb" src="img/bombb.png" alt="x" style="display: none;">
<img id="bombr" src="img/bombr.png" alt="y" style="display: none;">
<img id="bombr_step" src="img/bombr_step1.png" alt="y" style="display: none;">
<img id="bombb_step" src="img/bombb_step.png" alt="y" style="display: none;">
<img id="bombw" src="img/bombw.png" alt="y" style="display: none;">
<div>
<canvas id="myCanvas" width="1000" height="600"></canvas>
</div>
</body>
</html>
This is not a total answer because I do not know exactly what is required when dragging a bomb, but here are a few places to make changes to help further debugging:
the canvas element 'c' is declared in two places so when it comes to be used on mousedown etc it is undefined, the one with more local scope having been set up in the start function. I suggest removing the 'var' before the use of c in the start function so you pick up the one that has global scope.
In general, go through the code seeing where things are defined and if that is in the best position - look at the scope of all variables. The code in this respect is almost OK, for example you understand you cant draw an img on a canvas until it is downloaded, but a few things could do with tidying up.
There is a misspelling of 'length' (lenght) which means a function won't work.
In general use your browser's dev tools console to ensure you do not have anything undeclared or looking like null etc. Such Javascript errors will show there. Also use console.log at strategic points to make sure you understand the flow of the logic. Starting with console.log('at function xxx') at the beginning of the main functions might be useful. You can then see the order in which mousedown/up might happen on a bomb for example.
If you could describe further what should happen when a bomb is dragged that would be useful. Just for completeness here I reiterate what I said in a comment that the bombs are going to be treated like rectangles - so obscuring bombs behind them from the point of view of dragging - that may or may not matter to your application. There is a way round it if it is important, but it's probably better to get the basics sorted out first.

rotating SVG rect around its center using vanilla JavaScript

This is not a duplicate of the other question.
I found this talking about rotation about the center using XML, tried to implement the same using vanilla JavaScript like rotate(45, 60, 60) but did not work with me.
The approach worked with me is the one in the snippet below, but found the rect not rotating exactly around its center, and it is moving little bit, the rect should start rotating upon the first click, and should stop at the second click, which is going fine with me.
Any idea, why the item is moving, and how can I fix it.
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
var svg = SVG("svg");
svg.width='100%';
svg.height='100%';
document.body.appendChild(svg);
class myRect {
constructor(x,y,h,w,fill) {
this.SVGObj= SVG('rect'); // document.createElementNS(NS,"rect");
self = this.SVGObj;
self.x.baseVal.value=x;
self.y.baseVal.value=y;
self.width.baseVal.value=w;
self.height.baseVal.value=h;
self.style.fill=fill;
self.onclick="click(evt)";
self.addEventListener("click",this,false);
}
}
Object.defineProperty(myRect.prototype, "node", {
get: function(){ return this.SVGObj;}
});
Object.defineProperty(myRect.prototype, "CenterPoint", {
get: function(){
var self = this.SVGObj;
self.bbox = self.getBoundingClientRect(); // returned only after the item is drawn
self.Pc = {
x: self.bbox.left + self.bbox.width/2,
y: self.bbox.top + self.bbox.height/2
};
return self.Pc;
}
});
myRect.prototype.handleEvent= function(evt){
self = evt.target; // this returns the `rect` element
this.cntr = this.CenterPoint; // backup the origional center point Pc
this.r =5;
switch (evt.type){
case "click":
if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
else self.moving = false;
if(self.moving == true){
self.move = setInterval(()=>this.animate(),100);
}
else{
clearInterval(self.move);
}
break;
default:
break;
}
}
myRect.prototype.step = function(x,y) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(x,y));
}
myRect.prototype.rotate = function(r) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(r));
}
myRect.prototype.animate = function() {
self = this.SVGObj;
self.transform.baseVal.appendItem(this.step(this.cntr.x,this.cntr.y));
self.transform.baseVal.appendItem(this.rotate(this.r));
self.transform.baseVal.appendItem(this.step(-this.cntr.x,-this.cntr.y));
};
for (var i = 0; i < 10; i++) {
var x = Math.random() * 100,
y = Math.random() * 300;
var r= new myRect(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
svg.appendChild(r.node);
}
UPDATE
I found the issue to be calculating the center point of the rect using the self.getBoundingClientRect() there is always 4px extra in each side, which means 8px extra in the width and 8px extra in the height, as well as both x and y are shifted by 4 px, I found this talking about the same, but neither setting self.setAttribute("display", "block"); or self.style.display = "block"; worked with me.
So, now I've one of 2 options, either:
Find a solution of the extra 4px in each side (i.e. 4px shifting of both x and y, and total 8px extra in both width and height),
or calculating the mid-point using:
self.Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
The second option (the other way of calculating the mid-point worked fine with me, as it is rect but if other shape is used, it is not the same way, I'll look for universal way to find the mid-point whatever the object is, i.e. looking for the first option, which is solving the self.getBoundingClientRect() issue.
Here we go…
FIDDLE
Some code for documentation here:
let SVG = ((root) => {
let ns = root.getAttribute('xmlns');
return {
e (tag) {
return document.createElementNS(ns, tag);
},
add (e) {
return root.appendChild(e)
},
matrix () {
return root.createSVGMatrix();
},
transform () {
return root.createSVGTransformFromMatrix(this.matrix());
}
}
})(document.querySelector('svg.stage'));
class Rectangle {
constructor (x,y,w,h) {
this.node = SVG.add(SVG.e('rect'));
this.node.x.baseVal.value = x;
this.node.y.baseVal.value = y;
this.node.width.baseVal.value = w;
this.node.height.baseVal.value = h;
this.node.transform.baseVal.initialize(SVG.transform());
}
rotate (gamma, x, y) {
let t = this.node.transform.baseVal.getItem(0),
m1 = SVG.matrix().translate(-x, -y),
m2 = SVG.matrix().rotate(gamma),
m3 = SVG.matrix().translate(x, y),
mtr = t.matrix.multiply(m3).multiply(m2).multiply(m1);
this.node.transform.baseVal.getItem(0).setMatrix(mtr);
}
}
Thanks #Philipp,
Solving catching the SVG center can be done, by either of the following ways:
Using .getBoundingClientRect() and adjusting the dimentions considering 4px are extra in each side, so the resulted numbers to be adjusted as:
BoxC = self.getBoundingClientRect();
Pc = {
x: (BoxC.left - 4) + (BoxC.width - 8)/2,
y: (BoxC.top - 4) + (BoxC.height - 8)/2
};
or by:
Catching the .(x/y).baseVal.value as:
Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
Below a full running code:
let ns="http://www.w3.org/2000/svg";
var root = document.createElementNS(ns, "svg");
root.style.width='100%';
root.style.height='100%';
root.style.backgroundColor = 'green';
document.body.appendChild(root);
//let SVG = function() {}; // let SVG = new Object(); //let SVG = {};
class SVG {};
SVG.matrix = (()=> { return root.createSVGMatrix(); });
SVG.transform = (()=> { return root.createSVGTransformFromMatrix(SVG.matrix()); });
SVG.translate = ((x,y)=> { return SVG.matrix().translate(x,y) });
SVG.rotate = ((r)=> { return SVG.matrix().rotate(r); });
class Rectangle {
constructor (x,y,w,h,fill) {
this.node = document.createElementNS(ns, 'rect');
self = this.node;
self.x.baseVal.value = x;
self.y.baseVal.value = y;
self.width.baseVal.value = w;
self.height.baseVal.value = h;
self.style.fill=fill;
self.transform.baseVal.initialize(SVG.transform()); // to generate transform list
this.transform = self.transform.baseVal.getItem(0), // to be able to read the matrix
this.node.addEventListener("click",this,false);
}
}
Object.defineProperty(Rectangle.prototype, "draw", {
get: function(){ return this.node;}
});
Object.defineProperty(Rectangle.prototype, "CenterPoint", {
get: function(){
var self = this.node;
self.bbox = self.getBoundingClientRect(); // There is 4px shift in each side
self.bboxC = {
x: (self.bbox.left - 4) + (self.bbox.width - 8)/2,
y: (self.bbox.top - 4) + (self.bbox.height - 8)/2
};
// another option is:
self.Pc = {
x: self.x.baseVal.value + self.width.baseVal.value/2,
y: self.y.baseVal.value + self.height.baseVal.value/2
};
return self.bboxC;
// return self.Pc; // will give same output of bboxC
}
});
Rectangle.prototype.animate = function () {
let move01 = SVG.translate(this.CenterPoint.x,this.CenterPoint.y),
move02 = SVG.rotate(10),
move03 = SVG.translate(-this.CenterPoint.x,-this.CenterPoint.y);
movement = this.transform.matrix.multiply(move01).multiply(move02).multiply(move03);
this.transform.setMatrix(movement);
}
Rectangle.prototype.handleEvent= function(evt){
self = evt.target; // this returns the `rect` element
switch (evt.type){
case "click":
if (typeof self.moving == 'undefined' || self.moving == false) self.moving = true;
else self.moving = false;
if(self.moving == true){
self.move = setInterval(()=>this.animate(),100);
}
else{
clearInterval(self.move);
}
break;
default:
break;
}
}
for (var i = 0; i < 10; i++) {
var x = Math.random() * 100,
y = Math.random() * 300;
var r= new Rectangle(x,y,10,10,'#'+Math.round(0xffffff * Math.random()).toString(16));
root.appendChild(r.draw);
}

Random pattern while animating simulated gradient in canvas element

This is a query about something that popped up while I was experimenting with the canvas element via javascript. I wanted to have an array of points that formed a gradient which moved with time, which works perfectly apart from a bizarre pattern that comes up (only after the first wave or more), which also changes according to the number of columns and rows in the canvas (changing the size of the points just makes the patterns bigger or smaller, it's always on the same pixels.
Here's a little demo of what I mean with a bit of interface for you to mess around with, an example of the changing patterns is if the number of rows is changed to 0.75x the number of columns from the original (i.e. 40 columns, 30 rows).
http://codepen.io/zephyr/pen/GpwwWB
Javascript:
String.prototype.hexToRGBA = function(a) {
function cutHex(h) {
return (h.charAt(0) == "#") ? h.substring(1, 7) : h
}
var r = parseInt((cutHex(this)).substring(0, 2), 16);
var g = parseInt((cutHex(this)).substring(2, 4), 16);
var b = parseInt((cutHex(this)).substring(4, 6), 16);
return 'rgba(' + r.toString() + ',' + g.toString() + ',' + b.toString() + ',' + a.toString() + ')';
}
CanvasRenderingContext2D.prototype.clearDrawRect = function(shape) {
this.clearRect(shape.position.x, shape.position.y, shape.size, shape.size);
this.fillStyle = shape.color.base;
this.fillRect(shape.position.x, shape.position.y, shape.size, shape.size);
}
CanvasRenderingContext2D.prototype.render = function(render) {
(function animate() {
requestAnimationFrame(animate);
render();
})();
}
CanvasRenderingContext2D.prototype.renderAndThrottleFpsAt = function(fps, render) {
var fpsInterval, startTime, now, then, elapsed;
fpsInterval = 1000 / fps;
then = Date.now();
startTime = then;
(function animate() {
requestAnimationFrame(animate);
now = Date.now();
elapsed = now - then;
if (elapsed > fpsInterval) {
then = now - (elapsed % fpsInterval);
render();
}
})();
}
CanvasRenderingContext2D.prototype.pool = {};
CanvasRenderingContext2D.prototype.parsePoint = function(x, y, s, c) {
return {
color: c,
position: {
x: x,
y: y
},
size: s
}
}
CanvasRenderingContext2D.prototype.fillPointsPool = function(size, cols, rows, color) {
var i = cols;
var j = rows;
while(i--){
while(j--){
var x = i * size;
var y = j * size;
var a = (i * j) / (cols * rows);
var c = {
hex: color,
alpha: a,
dir: 1
};
if (typeof this.pool.points == 'undefined') {
this.pool.points = [this.parsePoint(x, y, size, c)];
} else {
this.pool.points.push(this.parsePoint(x, y, size, c));
}
}
j = rows;
}
}
CanvasRenderingContext2D.prototype.updatePointsPool = function(size, cols, rows, color) {
this.pool.points = [];
this.clearRect(0,0,this.canvas.width,this.canvas.height);
this.fillPointsPool(size, cols, rows, color);
}
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
// Populate Points
var size = document.getElementById('size');
var cols = document.getElementById('cols');
var rows = document.getElementById('rows');
var color = document.getElementById('color');
ctx.fillPointsPool(size.value, cols.value, rows.value, color.value);
size.oninput = function(){
ctx.updatePointsPool(this.value, cols.value, rows.value, color.value);
}
cols.oninput = function(){
ctx.updatePointsPool(size.value, this.value, rows.value, color.value);
}
rows.oninput = function(){
ctx.updatePointsPool(size.value, cols.value, this.value, color.value);
}
color.oninput = function(){
ctx.updatePointsPool(size.value, cols.value, rows.value, this.value);
}
ctx.renderAndThrottleFpsAt(60, function(){
var i = 0;
var len = ctx.pool.points.length;
while (i<len) {
var point = ctx.pool.points[i];
// Change alpha for wave
var delta = 0.01;
point.color.alpha = point.color.alpha + (delta * point.color.dir);
if (point.color.alpha > 1) {
point.color.dir = -1;
} else if (point.color.alpha <= 0) {
point.color.dir = 1;
}
// Calculate rgba value with new alpha
point.color.base = point.color.hex.hexToRGBA(point.color.alpha);
ctx.clearDrawRect(point);
i++;
}
});
Do any of you have an idea of what's causing the pattern to appear, and any suggestions on a fix for this?
Note: I will be changing the updatePointsPool function
You are forgetting to clamp your alpha values when you change the direction. The small error in the alpha value accumulates slowly producing the unwanted artifacts you see as the animation progresses.
To fix add the top and bottom limits to alpha in the code just after you add delta direction to alpha.
if (point.color.alpha > 1) {
point.color.alpha = 1; // clamp alpha max
point.color.dir = -1;
} else if (point.color.alpha <= 0) {
point.color.alpha = 0; // clamp alpha min
point.color.dir = 1;
}

Creating a bouncing box animation on canvas

I need to create an animation of dropping box, which supposed to bounce 10 times when it reaches a certain Y point on canvas, each time twice lower that the previous. So I have the animation of the dropping box, but I can't make the bounce work. Here are the functions that I wrote:
function dropBox(y, width, height) {
var img_box = new Image();
img_box.src = 'images/gift_box_small.png';
var box_y_pos = y;
if(y==0)
box_y_pos = y-img_box.naturalHeight;
img_box.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,width,height);
ctx_overlay.drawImage(img_box, (width/2)-(img_box.naturalWidth/2), box_y_pos);
ctx_overlay.restore();
}
box_y_pos += 3;
var box_bottom_position = box_y_pos - img_box.naturalHeight;
if(box_y_pos+img_box.naturalHeight<height-25)
var loopTimer = setTimeout(function() {dropBox(box_y_pos, width, height)},24);
else
bounceBox(img_box, box_y_pos, box_y_pos, (height/2)-(img_box.naturalHeight/2), "up");
}
function bounceBox(img, img_final_pos, y, midway_pos, direction){
var midway = midway_pos;
var direction = direction;
var img_y_pos = y;
img.onload = function(){
ctx_overlay.save();
ctx_overlay.clearRect(0,0,docWidth,docHeight);
ctx_overlay.drawImage(img, (docWidth/2)-(img.naturalWidth/2), img_y_pos);
ctx_overlay.restore();
}
for(var i = 0; i < 10; i++){
if(direction=="up"){
//going up
if(img_y_pos>midway_){
img_y_pos -= 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "up")},24);
} else {
img_y_pos += 3;
midway = Math.floor(midway /= 2);
if(midway%2>0)
midway += 1;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
} else {
//going down
if(img_y_pos < img_final_pos){
img_y_pos += 3;
var loopTimer = setTimeout(function() {bounceBox(img, img_final_pos, img_y_pos, midway_pos, "down")},24);
}
}
}
}
JSFiddle: http://jsfiddle.net/n2derqgw/3/
Why isn't it working and how can I make it work?
To avoid getting a headache, you've better handle the animation within a single function called with a setInterval.
And keep all animation-related data in one object.
So the code below does not exactly what you want, but should get you started :
http://jsfiddle.net/n2derqgw/4/
Setup :
var canvas_overlay, ctx_overlay, docWidth, docHeight;
var img_box = new Image();
img_box.src = 'http://corkeynet.com/test/images/gift_box_small.png';
var mustBeReadyCount = 2; // must load image and window
img_box.onload = launchWhenReady;
window.onload = launchWhenReady;
var animationStep = '';
var boxAnimationData = {
animationStep: '',
y: 0,
maxY: 0,
bounceCount: 6,
direction: -1,
bounceHeight: 0
};
function launchWhenReady() {
mustBeReadyCount--;
if (mustBeReadyCount) return;
docWidth = window.innerWidth;
docHeight = window.innerHeight;
canvas_overlay = document.getElementById('canvas_overlay');
ctx_overlay = canvas_overlay.getContext('2d');
resizeCanvas(docWidth, docHeight);
boxAnimationData.animationStep = 'falling';
boxAnimationData.bounceHeight = docHeight / 2 - img_box.height;
setInterval(animateBox, 30);
};
More interesting code is here :
function animateBox() {
if (boxAnimationData.animationStep == 'falling') dropBox();
else if (boxAnimationData.animationStep == 'bouncing') bounceBox();
}
function dropBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += 3;
if (boxAnimationData.y + img_box.height > docHeight) {
boxAnimationData.animationStep = 'bouncing';
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}
function bounceBox() {
ctx_overlay.clearRect(0, 0, docWidth, docHeight);
boxAnimationData.y += boxAnimationData.direction * 3;
if (boxAnimationData.y + img_box.height > docHeight) {
// reached floor ? swap direction
boxAnimationData.direction *= -1;
// and reduce jump height
boxAnimationData.bounceHeight *= 3 / 2;
boxAnimationData.bounceCount--;
if (!boxAnimationData.bounceCount) boxAnimationData.animationStep = '';
} else if (boxAnimationData.y < boxAnimationData.bounceHeight) {
boxAnimationData.direction *= -1;
}
ctx_overlay.drawImage(img_box, (docWidth / 2) - (img_box.width / 2), boxAnimationData.y);
}

Javascript Color Animation

I want to animate (transition) from 1 color to another in raw javascript.
I dont want to use any framework (jquery, mootools) or css3. plain raw javascript.
I have been really having trouble to do this, can someone help me out ? :)
maybe something like this:
lerp = function(a, b, u) {
return (1 - u) * a + u * b;
};
fade = function(element, property, start, end, duration) {
var interval = 10;
var steps = duration / interval;
var step_u = 1.0 / steps;
var u = 0.0;
var theInterval = setInterval(function() {
if (u >= 1.0) {
clearInterval(theInterval);
}
var r = Math.round(lerp(start.r, end.r, u));
var g = Math.round(lerp(start.g, end.g, u));
var b = Math.round(lerp(start.b, end.b, u));
var colorname = 'rgb(' + r + ',' + g + ',' + b + ')';
el.style.setProperty(property, colorname);
u += step_u;
}, interval);
};
You can play around an try it out as a jsfiddle or check out the full working example below. You might want to improve this by using HSL/HSV colors, which gives you a prettier transition, but i'll leave that up to you.
<html>
<head>
<title>Fade</title>
<style type="text/css">
#box {
width: 100px;
height: 100px;
background-color: rgb(255,0,0);
}
</style>
</head>
<body>
<div id="box"></div>
<script type="text/javascript" charset="utf-8">
// linear interpolation between two values a and b
// u controls amount of a/b and is in range [0.0,1.0]
lerp = function(a,b,u) {
return (1-u) * a + u * b;
};
fade = function(element, property, start, end, duration) {
var interval = 10;
var steps = duration/interval;
var step_u = 1.0/steps;
var u = 0.0;
var theInterval = setInterval(function(){
if (u >= 1.0){ clearInterval(theInterval) }
var r = parseInt(lerp(start.r, end.r, u));
var g = parseInt(lerp(start.g, end.g, u));
var b = parseInt(lerp(start.b, end.b, u));
var colorname = 'rgb('+r+','+g+','+b+')';
el.style.setProperty(property, colorname);
u += step_u;
}, interval);
};
// in action
el = document.getElementById('box'); // your element
property = 'background-color'; // fading property
startColor = {r:255, g: 0, b: 0}; // red
endColor = {r: 0, g:128, b:128}; // dark turquoise
fade(el,'background-color',startColor,endColor,1000);
// fade back after 2 secs
setTimeout(function(){
fade(el,'background-color',endColor,startColor,1000);
},2000);
</script>
</body>
</html>
Here is also my solution:
<html><head>
<script type="text/javascript">
<!--
function animate(id,color0,color1,duration){
//public attributes
this.elem = document.getElementById(id);
//private attributes
var r0= parseInt(color0.substring(0,2),16);
var g0= parseInt(color0.substring(2,4),16);
var b0= parseInt(color0.substring(4,6),16);
var r1= parseInt(color1.substring(0,2),16);
var g1= parseInt(color1.substring(2,4),16);
var b1= parseInt(color1.substring(4,6),16);
var wait = 100; //100ms
var steps = duration/wait;
var rstep = (r1 - r0) / (steps);
var gstep = (g1 - g0) / (steps);
var bstep = (b1 - b0) / (steps);
var self = this;
//public functions
this.step = function() {
steps--;
if ( steps>0 ) {
r0 = Math.floor(r0 + rstep);
g0 = Math.floor(g0 + gstep);
b0 = Math.floor(b0 + bstep);
elem.style.backgroundColor = 'rgb('+r0+','+g0+','+b0+')';
//alert(steps + ' ; ' + elem.style.backgroundColor);
window.setTimeout(function(){self.step();}, wait);
} else {
elem.style.backgroundColor = '#'+color1;
}
}
step();
//alert(this.r0);
}
//-->
</script>
</head><body>
<div id="anim" style="width:100px; height:100px; background-color:#ff0000"></div>
<input type="button" onclick="animate('anim','1122ff','ff2211',1000)" value="test" />
</body>
</html>
html at pastebin, how to call the timeout function - see for example 1, 2
if canvas would be ok you could try doing it like this ;)
var context = document.getElementsByTagName('canvas')[0].getContext('2d');
var hue = 0;
function bgcolor() {
hue = hue + Math.random() * 3 ;
context.fillStyle = 'hsl(' + hue + ', 100%, 50%)';
context.fillRect(0, 0, context.canvas.width, context.canvas.height);
}
setInterval(bgcolor, 20 );
Yes ;) it`s not perfect and just an excample but give it a try. Here is the complete pen on codepen.
One way might be to use setTimeout to call some function which incrementally changes the colour (I'm assuming background-color) by some small amount each time it's called. At each iteration, just check to see if you've arrived at your target colour and if not, increase or decrease your RGB value as necessary.

Categories

Resources