I have an animation object, AnimateJS, in the example below. It functions to draw lines along a spiral.
I would like to convert it to Snap's mina(). I'm have a bit of difficulty in applying the various mina properties to make this happen.
Any help would be appreciated.
<!DOCTYPE HTML>
<html>
<head>
<script type="text/javascript" src="http://svgDiscovery.com/_SNP/snap.svg-min.js"></script>
</head>
<body onload="setTimeout(runAnimLinear,1000)" >
<h4>Draw Spiraled Lines</h4>
<div style='width:90%;background-color:gainsboro;text-align:justify;padding:10px;border-radius:6px;'>
Use Snap to animate lines drawn along a spiral </div>
<table><tr>
<td>
<table>
<tr><td colspan=2><b>This Example's AnimateJS Settings:</b></td></tr>
<tr><td>1. Smoothness</td><td>80 frames per second</td></tr>
<tr><td>2. Duration</td><td>3000 - runtime in ms</td></tr>
<tr><td>3. Range</td><td> 720 degrees : ending/maximum value</td></tr>
<tr><td>4. Output Equation</td><td><span style=color:blue>function</span> linear(p){return p} </td></tr>
<tr><td>5. Application Output </td><td><span style=color:blue>function</span> addToSpiral(angle)</td></tr>
</table><p></p>
The above values to be used in <b>mina(a, A, b, B, get, set, [easing])</b> <br>
<textarea style=border-width:0px;width:400px;height:180px;>
Parameters:
a - start slave number
A - end slave number
b - start master number (start time in general case)
B - end master number (end time in general case)
get - getter of master number (see mina.time)
set - setter of slave number
easing - oneasing function, default is mina.linear
</textarea>
</td>
<td>
<div id="svgDiv" style='background-color:lightgreen;width:400px;height:400px;'>
<svg id="mySVG" width="400" height="400">
</svg>
</div>
<center><button disabled id=runAgainButton onClick=clearLines();runAnimLinear();this.disabled=true>run again</button></center>
</td>
</tr> </table>
</center>
<script>
var SNPsvg = Snap("#mySVG");
var SpiralG = SNPsvg.g().attr({id:'SpiralG',strokeWidth:1,stroke:'black' });
/*---generalized animate core function
Allows progress/output to follow a specific/customized equation(delta)
by: Ilya Kantor - http://javascript.info/tutorial/animation
*/
var AnimateJS=function(options){
this.options=options
var start = new Date
var iT = setInterval(
function(){
var timePassed = new Date - start
var progress = timePassed / options.duration
if (progress > 1) progress = 1
this.progress=progress
var delta = options.delta(progress)
options.output(delta)
if (progress == 1)clearInterval(iT);
},options.delay)
}
//--onload, and 'run again' button---
function runAnimLinear()
{
var range=720 //--degrees: 2 revs---
var FPS=80 //---Frames Per Second = smoothness--
var delay=1000/FPS //---delay---
var duration=3000 //---duration ms, 3 seconds---
var delta=function linear(p){return p}//---linear---
//---this animation---
new AnimateJS({delay:delay,duration:duration,delta:delta,output:
function(delta)//---output: delta=0.0 thru 1.0---
{
addToSpiral(range * delta )
if(progress==1) //---finished---
{
runAgainButton.disabled=false
}
}})
}
//---fired at each output request---
function addToSpiral(angle)
{
radius = Constant*angle;
offsetX = radius*Math.cos(angle*Math.PI/180);
offsetY = radius*Math.sin(angle*Math.PI/180);
currentX = basePointX+offsetX;
currentY = basePointY-offsetY;
// add perpendicular line segments...
lineX = lineHHLength*Math.cos(
branches*angle*Math.PI/180);
lineY = lineHHLength*Math.sin(
branches*angle*Math.PI/180);
fromX = currentX-lineX;
fromY = currentY+lineY;
destX = currentX+lineX;
destY = currentY-lineY;
lineNode = SNPsvg.line(fromX,fromY,destX,destY)
SpiralG.append(lineNode);
lineX = lineHHLength*Math.cos(
branches*(angle+90)*Math.PI/180);
lineY = lineHHLength*Math.sin(
branches*(angle+90)*Math.PI/180);
fromX = currentX-lineX;
fromY = currentY+lineY;
destX = currentX+lineX;
destY = currentY-lineY;
lineNode = SNPsvg.line(fromX,fromY,destX,destY)
SpiralG.append(lineNode);
}
//--fired on 'run again' ---
function clearLines()
{
SpiralG.clear()
}
//----spiral variables---
var basePointX = 180.;
var basePointY = 170.;
var currentX = 0.;
var currentY = 0.;
var offsetX = 0.;
var offsetY = 0.;
var radius = 0.;
var minorAxis = 12;
var majorAxis = 20.;
var Constant = 0.25;
var fromX = 0.;
var fromY = 0.;
var destX = 0.;
var destY = 0.;
var lineX = 0.;
var lineY = 0.;
var branches = 3.;
var lineHVLength = 2*minorAxis;
var lineHHLength = 2*majorAxis;
var lineNode = null;
</script>
</body>
</html>
Do you really need mina ? Snap has a generic animation method called Snap.animate() (docs here). This doesn't act on a specific element (unlike element.animate() ); So you rarely need to get down and dirty with mina specifically (don't think I have ever needed to), but appreciate this isn't answering your question specifically.
The first 2 arguments are the start and end values (note, it can take an array of values as well I think to interpolate between). And these will get interpolated between (like the delta I think in the original code).
3rd argument is the function to call each time. The val/delta gets passed into this function.
4th argument is the easing (so mina.linear wanted here).
5th argument is the callback (so we reset the button to run the animation again if wanted)
The main core is this converted function..
function runAnimLinear() {
var range=720 //--degrees: 2 revs---
var duration=3000 //---duration ms, 3 seconds---
Snap.animate(0, 1, function( delta ) {
addToSpiral( range * delta )
}, duration, mina.linear, function() { runAgainButton.disabled=false } );
}
The rest I've left as is.
However, there are some issues here now depending if things like smoothness are really needed, so it's not quite the same. If so, I 'think' it would need another solution, which would be a bit more complex and I'm not sure if there would then be enough of a reason to not use the original. If it's a specific reason you need to use mina, maybe add that to the question.
jsfiddle
Here is kind of the same thing using mina to give an idea of how that works.
var anim = mina( 0, 1, mina.time(), mina.time() + duration, mina.time,
function( delta ) { addToSpiral( range * delta )})
//callback using eve, referencing the id of the animation from above
eve.once("mina.finish." + anim.id, function () {
runAgainButton.disabled=false
});
jsfiddle
Related
Im creating an object that randomly moves in a natural way using noise like this (works as intended):
The objects encounter a collision and their trajectory is manipulated, the movement path now changes to straight line (words as intended)
thisRabbit.x = _world.width * (noise(thisRabbit.t));
thisRabbit.y = _world.height * (noise(thisRabbit.t+5));
thisRabbit.t += 0.001;
The problem is after this movement , i want the object to start moving in a random direction again as it was initially. If i use the same function, the object jumps to the last location before the trajectory was modified.
let vx = this.acquiredFood[0] - this.x;
let vy = this.acquiredFood[1] - this.y;
let f = (this.genes.speed + 10) / Math.sqrt(vx*vx+vy*vy);
vx = vx * f;
vy = vy * f;
let newX = this.x + vx;
let newY = this.y + vy;
So how do i get the object to move as before, given a starting position
edit: snippet here: https://editor.p5js.org/vince.chinner/sketches/HPFKR8eIw
Your problem is that you used a factor from 0 to 1 generated with noise and an incremented seed to generate the position by multiplying directly the world dimentions. When reaching food, you cannot increment the seed as to be in the exact position where the movement to get your food led you (I found no inverse function for noise to get the seed from the return value).
What you need to do instead is use the noise to increment or decrement the coordinates, so that no matter where the seed is, you don't loose your current position.
Here are the different corrections I applied to the code, as there were also syntax errors, I can't really paste the whole stuff here for copyright reasons (you didn't share the whole code here and the sketch belongs to you)
MAIN CORRECTION:
used a var found because returning from the forEach callback doesn't make you leave the findFood function, but the callback one. And the forEach loop doesn't stop. Using this var prevents the further forEach tests to be made and allows you to return from findFood so that no further move is made after seeing food.
noise is now applied to a value of 4 and I subtract 2, so that x and y now change with a range of -2 to 2 each. Of course, with this method, you need to check against world dimentions or else the rabbit could leave the world. The seed increment has been changed too or else it would vary too slowly (adapt values as you wish)
findFood(){
var thisRabbit = this, found = false;
_world.food.forEach(f => {
if(!found){
let d = int(dist(f[0], f[1], thisRabbit.x, thisRabbit.y));
if(d < (thisRabbit.genes.vision / 2)+3){
thisRabbit.state = "foundFood";
this.acquiredFood = f;
found = true;
}
}
});
if(found){ return; }
thisRabbit.x += (noise(thisRabbit.t) * 4) - 2;
if(thisRabbit.x < 0){ thisRabbit.x = 0; }
if(thisRabbit.x > _world.width){ thisRabbit.x = _world.width; }
thisRabbit.y += (noise(thisRabbit.t + 5) * 4) - 2;
if(thisRabbit.y < 0){ thisRabbit.y = 0; }
if(thisRabbit.y > _world.height){ thisRabbit.y = _world.height; }
thisRabbit.t += 0.01;
}
SYNTAX ERRORS:
lines 23 / 24: assignment should be with a value (null or false)
this.genes = null;
this.acquiredFood = null;
lines 129 to 133: end you instructions with a ; instead of a ,
this.width = w;
this.height = h;
this.foodDensity = foodDensity;
this.food = [];
this.rabits = [];
line 156 to 160: there should be no space between rabbit and .t. Additionnally, because the coordinates are not directly linked to t, I would prefer to use random for starting position:
let x = this.width * random();
let y = this.height * random();
let _rabbit = new rabbit(x, y);
_rabbit.genes = genes;
_rabbit.t = t;
I'm trying to make the Logo Turtle in HTML 5 with javascript and canvas (I want to study simple algorithms with my students, and i want to make easy instructions).
I successfully made basic instructions, but the lines appear all at the same time, and i want to see them appear one after one.
Here is my code :
var dessin = document.getElementById("dessin")
var ctx = dessin.getContext("2d");
var angle = 0; // angle en degrés
// on donne les coordonnées de départ
var x = dessin.width / 2;
var y = dessin.height / 2;
function forward(distance) {
var iter = 1;
var Angle = angle % 360;
var theta = Angle / 180 * Math.PI;
var vitesse = 10;
var compteur = 1;
var timer = setInterval(function() {
ctx.beginPath();
ctx.moveTo(x, y);
x = Math.cos(theta) * distance / vitesse + x;
y = Math.sin(theta) * distance / vitesse + y;
ctx.lineTo(x, y);
ctx.stroke();
compteur++;
if (compteur > vitesse) {
clearInterval(timer);
}
}, 1000 / vitesse);
//setTimeout(clearInterval(timer),2000);
}
function turn_left(angle_rotation) {
angle = (angle - angle_rotation) % 360;
}
//Firing commands
turn_left(45);
forward(100);
turn_left(45);
forward(100);
<canvas id="dessin" width="400" height="400"></canvas>
I want to have two lines (a diagonal one, and a vertical one), but i have lot of it...
How can i do that ?
Thanks !
PS : I don't speak English very well, my apologies...
Your problem is your code is asynchrone. To achieve what you want to do, you need an animations manager.
Here, our animations manager is just two var : one is a boolean to know if we are moving, another is an array which accumulates queued animations:
var moveManager = [];
var isMoving = false;
I also make timer global because we have only one animation at a time :
var timer;
After you need to make the logic of your animations manager which is : If i'm not moving and I have a queued animation so play it :
function nextMove() {
if (!isMoving && moveManager.length) {
var move = moveManager.shift();
move();
}
}
And, last thing, each animation manages itself start, stop and call to next animation :
function turn_left(angle_rotation) {
moveManager.push(function() {
isMoving = true;
angle = (angle - angle_rotation) % 360 ;
isMoving = false;
nextMove();
});
}
With all this, you can continue your turtle.
Working jsFiddle => https://jsfiddle.net/y9efewqb/5/
PS : I use your code to explain how to make your turle but some part should be optimized (use requestAnimationFrame instead of using setInterval, make all this in classes to avoid global var, ...)
Please bear with me. I appreciate any pointers / patience and help in understanding the code pasted below.
All the lines marked with the '// ??' comment is where I don't have a clue what's happening.
I picked up the Building an HTML5 Game book (http://buildanhtml5game.com/) in the hopes of learning how to write a game, more especially in HTML5 and JavaScript.
To my surprise, the book does not really explain various background information or assumes the readers already know their way around.
I am not a mathematics expert and that's one big reason I am stuck with the code below. The book barely touches the surface and the explanation is in a line or two (leaving me more confused).
Please, I may ask, do provide me with links so I study the mathematics behind the formulas below and write some English explanation as well so I understand.
I don't want to skip the mathematics parts in this book because it defeats the purpose.
Much appreciated.
var BubbleShoot = window.BubbleShoot || {};
BubbleShoot.CollisionDetector = (function($){
var CollisionDetector = {
findIntersection : function(curBubble,board,angle) {
// 'angle' is the angle between the 'curBubble' starting bubble
// at the center of the screen and the mouse click's direction aimed at the bubbles
// above.
// I understand how that angle is computed in previous code snippets.
// 'board' contains an array of bubble objects that are static and laid up on the board
// waiting to be shot at with 'curbubble' and 'angle'.
// Get the bubble rows container
var rows = board.getRows();
var collision = null;
// Get the bubble's that will be shot from the center coordinates
var pos = curBubble.getSprite().position();
// The start coordinates are the bubble's center
var start = {
left : pos.left + BubbleShoot.ui.BUBBLE_DIMS/2,
top : pos.top + BubbleShoot.ui.BUBBLE_DIMS/2
};
// ?? (A) what does this give us (mathematically speaking, what are those variables called)
var dx = Math.sin(angle);
var dy = -Math.cos(angle);
for(var i=0;i<rows.length;i++){
// for each row
var row = rows[i];
for(var j=0;j<row.length;j++){
// Get bubble at i,j
var bubble = row[j];
if(bubble){
// Get bubble's coordinates. we want to see if the starting bubble
// will collidate with this static bubble if the starting bubble was fired
// with an angle 'angle'
var coords = bubble.getCoords();
// ?? (B) that's not the distance between the two points!
// ?? what do you call this mathematically?
var distToBubble = {
x : start.left - coords.left,
y : start.top - coords.top
};
// ?? (C) what is 't'? what does it give? can you please give me reference
// ?? so I study the proper math behind its purpose?
var t = dx * distToBubble.x + dy * distToBubble.y;
// ?? (D) why '-t' * dx and what does this give us?
var ex = -t * dx + start.left;
// ?? (E) same for 'ey'
var ey = -t * dy + start.top;
// distance between the two points (ex,ey) and (coords.left,coords.top)
// ?? (F) I am not sure what are 'ex' and 'ey'
var distEC = Math.sqrt((ex - coords.left) * (ex - coords.left) +
(ey - coords.top) * (ey - coords.top));
if(distEC<BubbleShoot.ui.BUBBLE_DIMS * .75){
// ?? (G) dt is what?
var dt = Math.sqrt(BubbleShoot.ui.BUBBLE_DIMS * BubbleShoot.
ui.BUBBLE_DIMS - distEC * distEC);
// ?? (H) what's this?
var offset1 = {
x : (t - dt) * dx,
y : -(t - dt) * dy
};
// ?? (I) and this?!
var offset2 = {
x : (t + dt) * dx,
y : -(t + dt) * dy
};
// ?? (J) distance between something
var distToCollision1 = Math.sqrt(offset1.x * offset1.x +
offset1.y * offset1.y);
var distToCollision2 = Math.sqrt(offset2.x * offset2.x +
offset2.y * offset2.y);
if(distToCollision1 < distToCollision2){
var distToCollision = distToCollision1;
var dest = {
x : offset1.x + start.left,
y : offset1.y + start.top
};
}else{
var distToCollision = distToCollision2;
var dest = {
x : -offset2.x + start.left,
y : offset2.y + start.top
};
}
if(!collision || collision.distToCollision>distToCollision){
collision = {
bubble : bubble,
distToCollision : distToCollision,
coords : dest
};
};
};
};
};
};
return collision;
}
};
return CollisionDetector;
})(jQuery);
You can play the game here just to see how it looks: http://buildanhtml5game.com/?page_id=20
I need to do something along the lines of this, but I with Snap.svg and with the ability to:
Drag the entire group along the path
Preserving spacing during the drag
Allow group drag from any group item
Support any number of group items
Support various different shaped paths
I started this jsfiddle as a working starting point (and also posted below), but I'm at a loss at how best to attack the problem.
var paper = Snap('#panel');
var path = paper.path('M44.16,44.16 L44.16,44.16 L73.6,14.719999999999999 L132.48,73.6 L14.719999999999999,191.35999999999999 L132.48,309.12 L103.03999999999999,338.55999999999995 L44.16,279.67999999999995 L44.16,279.67999999999995')
.attr({
stroke: 'gray',
strokeWidth: 3,
fill: 'none'
});
var c1 = paper.circle(103.03999999999999, 103.03999999999999, 15);
var c2 = paper.circle(44.16, 161.92, 15);
var c3 = paper.circle(73.6, 132.48, 15);
var cGroup = paper.g();
cGroup.add(c1,c2,c3);
This quite a tricky one overall. Here's most of the solution which should at least set you off right as one possible method. For the distance checking, I used the code in the original fiddle, so credit to the person who wrote that, as its potentially tricky (and maybe worthy of its own SO question, I think it will need a tweak though).
fiddle here edit: You'll need to tweak to allow for starting position better.
Drag the circle to start it off, as I haven't set the start positions. You will want to adjust the elements starting positions, depending on whether you will zero offset them or whatever (otherwise you will need to allow for this when moving/transforming). You may also want to check for if the first/last element reaches the end and stops them all, so they all stop if one element reaches the path end.
It works by putting all the of the objects in a set, and attaching a handler to each of them (you could possibly just have one handler on the group, more elegant but may be a bit trickier).
We keep track of each elements index
this.data('index')
So when it comes to moving them along the line, we know where it is in the 'chain' and can offset to compensate, ie the following line...
var whichDrag = this;
....
mySet.forEach( function( el, i ) {
var which = whichDrag.data("index") - i;
pt = path.getPointAtLength(l + (which * spacer ));
if( !isNaN(pt.x) && !isNaN(pt.x) ) { // check if over end
el.transform('t' + pt.x + ',' + pt.y );
};
} );
Complete code...
var paper = Snap('#panel');
var spacer = 70;
var path = paper.path('M44.16,44.16 L44.16,44.16 L73.6,14.719999999999999 L132.48,73.6 L14.719999999999999,191.35999999999999 L132.48,309.12 L103.03999999999999,338.55999999999995 L44.16,279.67999999999995 L44.16,279.67999999999995')
.attr({
stroke: 'gray',
strokeWidth: 3,
fill: 'none'
});
var pt = path.getPointAtLength(l);
//e = r.ellipse(pt.x, pt.y, 4, 4).attr({stroke: "none", fill: "#f00"}),
var totLen = path.getTotalLength();
var r1 = paper.rect(0,0,10,10);
var c3 = paper.circle(0,0, 15);
var c2 = paper.circle(0,0, 15);
var c1 = paper.circle(0,0, 15);
var l = 0;
var searchDl = 1;
var cGroup = paper.g();
cGroup.add(c3,c2,c1,r1);
var mySet = cGroup.selectAll("*");
start = function () {
this.data("ox", +this.getBBox().cx );
this.data("oy", +this.getBBox().cy );
this.attr({opacity: 1});
},
move = function (dx, dy) {
var whichDrag = this;
var tmpPt = {
x : this.data("ox") + dx,
y : this.data("oy") + dy
};
// move will be called with dx and dy
l = gradSearch(l, tmpPt);
pt = path.getPointAtLength(l);
// this.attr({cx: pt.x, cy: pt.y});
mySet.forEach( function( el, i ) {
var which = whichDrag.data("index") - i;
pt = path.getPointAtLength(l + (which * spacer ));
if( !isNaN(pt.x) && !isNaN(pt.x) ) {
//el.attr({cx: pt.x, cy: pt.y});
el.transform('t' + pt.x + ',' + pt.y );
};
} );
},
up = function () {
// restoring state
this.attr({opacity: 1});
},
gradSearch = function (l0, pt) {
l0 = l0 + totLen;
var l1 = l0,
dist0 = dist(path.getPointAtLength(l0 % totLen), pt),
dist1,
searchDir;
if (dist(path.getPointAtLength((l0 - searchDl) % totLen), pt) >
dist(path.getPointAtLength((l0 + searchDl) % totLen), pt)) {
searchDir = searchDl;
} else {
searchDir = -searchDl;
}
l1 += searchDir;
dist1 = dist(path.getPointAtLength(l1 % totLen), pt);
while (dist1
I have the following code for a complex function plotter. It creates a phase plot of the complex function f(z) = z*(z+5)(z-v) where v is where your mouse is pointing. As you can see, it is pretty slow. Is there any way to speed this up and get a smooth animation? Just pointing me in the right direction would be helpful.
<html>
<head>
<script type="text/javascript" src="jquery-1.8.1.js"></script>
<script type="application/javascript">
function draw() {
var canvas = document.getElementById("canvas");
var ctx;// = canvas.getContext("2d");
//The following functions convert pixel Xs and Ys to real and imaginary
//parts of a complex number, and back again
var pixToReal = function(n){return n/15.0-10.0};
var pixToImag = function(n){return - n/15.0+10}
var realToPix = function(x){return Math.round((x+10.0)*15)}
var imagToPix = function(y){return Math.round((-y+10.0)*15)}
//Storing the complex number a+bi as [a,b], the following functions add,
//multiply, and find the modulus of the complex number
var add = function(z1,z2){return [z1[0]+z2[0],z1[1] + z2[1]]}
var mult = function(z1,z2){return [z1[0]*z2[0]-z1[1]*z2[1],z1[0]*z2[1]+z1[1]*z2[0]]}
var modulus = function(z){
if (z[1]>0){return Math.atan2(z[1],z[0])}
else {return Math.atan2(z[1],z[0])+2*Math.PI}
};
//Takes a complex number and returns the RGB value of the corresponding
//point on the color wheel.
var complexToRGB = function(z){
var theta = modulus(z)%(2*Math.PI)
var Hp = (theta/(2*Math.PI))*6
var X = Math.abs(Math.round((1 - Math.abs(Hp%2 -1))*255))
var C = "rgb(0,0,0)"
if (Hp>=0 && Hp<1){
C = "rgb("+255+","+X+",0)"
};
if (1<=Hp && Hp<2){
C = "rgb("+X+","+255+",0)"}
if (2<=Hp && Hp<3){
C = "rgb("+0+","+255+","+X+")"}
if (3<=Hp && Hp<4){
C = "rgb("+0+","+X+","+255+")"}
if (4<=Hp && Hp<5){
C = "rgb("+X+","+0+","+255+")"}
if (5<=Hp && Hp<6){
C = "rgb("+255+","+0+","+X+")"}
return C
}
//a complex number
var v = [0,4]
//the function f(z) = z*(z+5)*(z+v)
var f = function(z){return mult(add(mult(z,z),mult([5,5],z)),add(z,v))}
//makes v the opposite complex number your mouse is pointing at,
//i.e. your mouse points at a root of f
function onMouseMove(evt) {
v = [-pixToReal(evt.pageX), -pixToImag(evt.pageY)];
}
$(document).mousemove(onMouseMove);
makeFrame = function(){
ctx.clearRect(0,0,300,300);
for (var n =0;n<300;n++){
for (var m=0;m<300;m++){
var x = pixToReal(n)
var y = pixToImag(m)
var z = [x,y]
var w = f(z)
ctx.fillStyle = complexToRGB(w)
ctx.fillRect(n,m,1,1)
}
}
}
function animate() {
ctx = canvas.getContext("2d");
return setInterval(makeFrame, 1);
}
animate();
}
</script>
</head>
<body onload="draw()">
<canvas id="canvas" width="300" height="300"></canvas>
</body>
I have made some quick optimizations that speeds it up about 500%. I think you could speed it up further but it would require a bit more work.
What I have done is:
Instead of setting the pixel values using fillStyle and fillRect, all pixel values are retrieved as an array (imageData), and then makeFrame() manipulates the imageData array and then set all pixels at once using putImageData().
The change above required that complexToRGB() retuns an array with the red, green and blue color values instead of a string.
in the complexToRGB() function the list of if-cases has been changed to a chain of if-else (which is faster since the conditions after a true condition will not be evaluted).
Changed the setInterval from 1000 fps to 25. There's no way the algorithm will be able to keep up with that framerate, so it's better to set it to a more realistic frame rate.
Here's the code as a jsFiddle.
Next steps: I would also try to remove as many function calls as possible, for instance inline the pixToReal() and pixToImag() formulas in the inner for loop:
for (var m = 0; m < 300; m++) {
var x = n / 15.0 - 10.0;
var y = -m / 15.0 + 10;
And then optimize the code in complexToRGB() and consider doing the same to that function to remove that function call.
I made a fiddle here, using requestAnimationFrame and drawing with ImageData. Works pretty well, maybe you can merge mine with strille's approach.