why this functions can't be found outside the setInterval loop? (javascript) - javascript

I'm using planetaryjs package to draw a globe in js.
There's a function planetary.plugins.pings.add. It works when it's in this loop:
setInterval(function() {
var lat = 30.2500;
var lng = 120.1667;
var color = 'white';
globe.plugins.pings.add(lng, lat, { color: color, ttl: 30000, angle: Math.random() * 10 });
}, 200);
But I only want to draw one ping, so I did
var lat = 30.2500;
var lng = 120.1667;
var color = 'white';
globe.plugins.pings.add(lng, lat, { color: color, ttl: 30000, angle: Math.random() * 10 });
But firefox tells me
TypeError: globe.plugins.pings is undefined
Does somebody know why is this? Complete code is here (see line 67-77). Source is here
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type='text/javascript' src='http://d3js.org/d3.v3.min.js'></script>
<script type='text/javascript' src="http://d3js.org/topojson.v1.min.js"></script>
<script type='text/javascript' src="http://labs.rivendellweb.net/data-vis/planetary/planetaryjs.js"></script>
</head>
<body>
<canvas id='rotatingGlobe' width='800' height='600' style='width: 800px; height: 600px; cursor: move;'></canvas>
<script>
(function() {
var globe = planetaryjs.planet();
// Load our custom `autorotate` plugin; see below.
globe.loadPlugin(autorotate(0));
// The `earth` plugin draws the oceans and the land; it's actually
// a combination of several separate built-in plugins.
// Note that we're loading a special TopoJSON file
// (world-110m-withlakes.json) so we can render lakes.
globe.loadPlugin(planetaryjs.plugins.earth({
topojson: { file: 'world-110m-withlakes.json' },
oceans: { fill: '#000080' },
land: { fill: '#339966' },
borders: { stroke: '#008000' }
}));
// Load our custom `lakes` plugin to draw lakes; see below.
globe.loadPlugin(lakes({
fill: '#000080'
}));
// The `pings` plugin draws animated pings on the globe.
globe.loadPlugin(planetaryjs.plugins.pings());
// The `zoom` and `drag` plugins enable
// manipulating the globe with the mouse.
globe.loadPlugin(planetaryjs.plugins.zoom({
scaleExtent: [100, 2000]
}));
globe.loadPlugin(planetaryjs.plugins.drag({
// Dragging the globe should pause the
// automatic rotation until we release the mouse.
onDragStart: function() {
this.plugins.autorotate.pause();
},
onDragEnd: function() {
this.plugins.autorotate.resume();
}
}));
// Set up the globe's initial scale, offset, and rotation.
globe.projection
.scale(400)
.translate([400, 300])
.rotate([-100, -30, 0]);
// Every few hundred milliseconds, we'll draw another random ping.
//var colors = ['red', 'yellow', 'white', 'orange', 'green', 'cyan', 'pink'];
setInterval(function() {
var lat = 30.2500;
var lng = 120.1667;
var color = 'white';
globe.plugins.pings.add(lng, lat, { color: color, ttl: 30000, angle: Math.random() * 10 });
}, 200);
var lat = 30.2500;
var lng = 120.1667;
var color = 'white';
globe.plugins.pings.add(lng, lat, { color: color, ttl: 30000, angle: Math.random() * 10 });
var canvas = document.getElementById('rotatingGlobe');
// Special code to handle high-density displays (e.g. retina, some phones)
// In the future, Planetary.js will handle this by itself (or via a plugin).
if (window.devicePixelRatio == 2) {
canvas.width = 800;
canvas.height = 800;
context = canvas.getContext('2d');
context.scale(2, 2);
}
// Draw that globe!
globe.draw(canvas);
// This plugin will automatically rotate the globe around its vertical
// axis a configured number of degrees every second.
function autorotate(degPerSec) {
// Planetary.js plugins are functions that take a `planet` instance
// as an argument...
return function(planet) {
var lastTick = null;
var paused = false;
planet.plugins.autorotate = {
pause: function() { paused = true; },
resume: function() { paused = false; }
};
// ...and configure hooks into certain pieces of its lifecycle.
planet.onDraw(function() {
if (paused || !lastTick) {
lastTick = new Date();
} else {
var now = new Date();
var delta = now - lastTick;
// This plugin uses the built-in projection (provided by D3)
// to rotate the globe each time we draw it.
var rotation = planet.projection.rotate();
rotation[0] += degPerSec * delta / 1000;
if (rotation[0] >= 180) rotation[0] -= 360;
planet.projection.rotate(rotation);
lastTick = now;
}
});
};
};
// This plugin takes lake data from the special
// TopoJSON we're loading and draws them on the map.
function lakes(options) {
options = options || {};
var lakes = null;
return function(planet) {
planet.onInit(function() {
// We can access the data loaded from the TopoJSON plugin
// on its namespace on `planet.plugins`. We're loading a custom
// TopoJSON file with an object called "ne_110m_lakes".
var world = planet.plugins.topojson.world;
lakes = topojson.feature(world, world.objects.ne_110m_lakes);
});
planet.onDraw(function() {
planet.withSavedContext(function(context) {
context.beginPath();
planet.path.context(context)(lakes);
context.fillStyle = options.fill || 'black';
context.fill();
});
});
};
};
})();
</script>
</body>
</html>

Replace setInterval by setTimeout.
The reason your direct call fails is because globe.plugins.pings is not initialized till after globe.draw(canvas); is called. You could also move it to after this.
When compared to replacing it by the code block, setTimeout moves the execution of the code block to the end of the execution queue i.e. till after globe.draw(canvas); is called and globe.plugins.pings is initialized - but unlike setInterval, it runs only once.

it would be better to use some sort of callback rather than just replying on a random timeout.
Something like this.
planet.onInit( function([done]){} )

The DOM is not initialized at the point of execution, you should wrap the initialization in document.addEventListener('DOMContentLoaded', function () { /* your code here */ });

Related

Paint text on the mouse hover on the graphics in the position of cursor

I have a problem when I go to paint the PIXI.Text in the cursor position.
This is the simple demo to reproduce the problem, when you go over the node with the cursor I paint the text, in this case, "#author vincenzopalazzo" but I want the position on the node, so I think for resolving the problem I have got the solution I must set the position of the mouse.
But I don't have an idea got get this position, so this is an example to reproduce the problem with PIXI
//setup Pixi renderer
var renderer = PIXI.autoDetectRenderer(600, 400, {
backgroundColor: 0x000000
});
document.body.appendChild(renderer.view);
// create new stage
var stage = new PIXI.Container();
// create helpful message
var style = {
font: '18px Courier, monospace',
fill: '#ffffff'
};
// create graphic object called circle then draw a circle on it
var circle = new PIXI.Graphics();
circle.lineStyle(5, 0xFFFFFF, 1);
circle.beginFill(0x0000FF, 1);
circle.drawCircle(150, 150, 100);
circle.endFill();
circle.alpha = 0.5;
stage.addChild(circle);
// designate circle as being interactive so it handles events
circle.interactive = true;
// create hit area, needed for interactivity
circle.hitArea = new PIXI.Circle(150, 150, 100);
// make circle non-transparent when mouse is over it
circle.mouseover = function(events) {
var message = new PIXI.Text('Hover your mouse over the circle to see the effect.', style);
message.x = events.clientX;
message.y = events.clientY;
circle.message = message;
circle.addChild(message);
}
// make circle half-transparent when mouse leaves
circle.mouseout = function(mouseData) {
this.alpha = 0.5;
circle.removeChild(circle.message);
delete circle.message;
}
// start animating
animate();
function animate() {
requestAnimationFrame(animate);
// render the root container
renderer.render(stage);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.4/pixi.js"></script>
This is my real code
module.exports = function (animatedNode, ctx) {
ctx.on('hover', function(animatedNode, ctx){
let x = animatedNode.pos.x;
let y = - animatedNode.pos.y / 2;
if(animatedNode.label === undefined){
animatedNode.label = new PIXI.Text('#author vincenzopalazzo', { fontFamily: "Arial", fontSize: "20px" , fill: 0x000000} );
animatedNode.label.x = x;
animatedNode.label.y = y + animatedNode.width/2;
ctx.addChild(animatedNode.label);
}else{
animatedNode.label.x = x;
animatedNode.label.y = y + animatedNode.width/2;
}
});
ctx.on('unhover', function(animatedNode, ctx){
ctx.removeChild(animatedNode.label);
delete animatedNode.label;
});
ctx.mouseover = function() {
console.debug('I\'call the hover events');
this.fire('hover', animatedNode, ctx);
}
ctx.mouseout = function() {
console.debug('I\'call the unhover events');
this.fire('unhover', animatedNode, ctx);
}
}
I'm using the ngraph.events on the ctx (it is the PIXI graphics) object, the method on and fire is the module nghraph.events
In your example code (first snippet) the "moseover" handler should be changed from:
// make circle non-transparent when mouse is over it
circle.mouseover = function(events) {
var message = new PIXI.Text('Hover your mouse over the circle to see the effect.', style);
message.x = events.clientX;
message.y = events.clientY;
circle.message = message;
circle.addChild(message);
}
to:
// make circle non-transparent when mouse is over it
circle.on('mouseover', function(event) {
// console.log('mouse is over the circle');
// console.log(event); // see in (for example in Chrome Devtools console) what is inside this variable
var message = new PIXI.Text('Hover your mouse over the circle to see the effect.', style);
// By looking at what "console.log(event)" shows we can see that instead of:
// message.x = events.clientX;
// message.y = events.clientY;
// should be:
message.x = event.data.global.x;
message.y = event.data.global.y;
circle.message = message;
circle.addChild(message);
});
To understand it more you can uncomment the "console.log" lines to observe it in your browser devtools console.
Then we also need to handle 'mouseover' event like this:
circle.on('mousemove',function (event) {
if (!circle.message) {
return;
}
var newPosition = event.data.getLocalPosition(this.parent);
circle.message.x = newPosition.x;
circle.message.y = newPosition.y;
});
so whole runnable example will be like this:
//setup Pixi renderer
var renderer = PIXI.autoDetectRenderer(600, 400, {
backgroundColor: 0x000000
});
document.body.appendChild(renderer.view);
// create new stage
var stage = new PIXI.Container();
// create helpful message
var style = {
font: '18px Courier, monospace',
fill: '#ffffff'
};
// create graphic object called circle then draw a circle on it
var circle = new PIXI.Graphics();
circle.lineStyle(5, 0xFFFFFF, 1);
circle.beginFill(0x0000FF, 1);
circle.drawCircle(150, 150, 100);
circle.endFill();
circle.alpha = 0.5;
stage.addChild(circle);
// designate circle as being interactive so it handles events
circle.interactive = true;
// create hit area, needed for interactivity
circle.hitArea = new PIXI.Circle(150, 150, 100);
// make circle non-transparent when mouse is over it
circle.on('mouseover', function(event) {
// console.log('mouse is over the circle');
// console.log(event); // see in (for example in Chrome Devtools console) what is inside this variable
var message = new PIXI.Text('Hover your mouse over the circle to see the effect.', style);
// By looking at what "console.log(event)" shows we can see that instead of:
// message.x = events.clientX;
// message.y = events.clientY;
// should be:
message.x = event.data.global.x;
message.y = event.data.global.y;
circle.message = message;
circle.addChild(message);
});
circle.on('mousemove',function (event) {
if (!circle.message) {
return;
}
var newPosition = event.data.getLocalPosition(this.parent);
circle.message.x = newPosition.x;
circle.message.y = newPosition.y;
});
// make circle half-transparent when mouse leaves
circle.mouseout = function(mouseData) {
this.alpha = 0.5;
circle.removeChild(circle.message);
delete circle.message;
}
// start animating
animate();
function animate() {
requestAnimationFrame(animate);
// render the root container
renderer.render(stage);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/5.1.4/pixi.js"></script>
Please also see:
"Interaction/Dragging" Pixi.js demo (with source code): https://pixijs.io/examples/#/interaction/dragging.js
tutorial "Rotate towards mouse and shoot in that direction" by Igor Neuhold : http://proclive.io/shooting-tutorial/
Pixi.js API reference:
https://pixijs.download/dev/docs/PIXI.DisplayObject.html#event:mousemove
https://pixijs.download/dev/docs/PIXI.interaction.InteractionEvent.html
https://pixijs.download/dev/docs/PIXI.interaction.InteractionData.html

KineticJS, Paint like program, brush gaps

I am trying to do something like paint with KineticJS. I am trying to draw the color with circles that originate from the mouse position. However the eventlistener of the mouse position seems too slow and when I move the mouse too fast the circles drawn are far from each other resulting this:
I have seen people filling array with points drawing lines between them, but I thought thats very bad for optimization because after dubbing the screen too much the canvas starts lagging because it has too much lines that it redraws every frame. I decided to cancel the cleaning of the layer and I am adding new circle at the current mouse position and I remove the old one for optimization. However since Im not drawing lines on fast mouse movement it leaves huge gaps. I would be very grateful if anyone can help me with this.
Here is my code:
(function() {
var stage = new Kinetic.Stage({
container: 'main-drawing-window',
width: 920,
height: 750
}),
workplace = document.getElementById('main-drawing-window'),
layer = new Kinetic.Layer({
clearBeforeDraw: false
}),
border = new Kinetic.Rect({
stroke: "black",
strokeWidth: 2,
x: 0,
y: 0,
width: stage.getWidth(),
height: stage.getHeight()
}),
brush = new Kinetic.Circle({
radius: 20,
fill: 'red',
strokeWidth: 2,
x: 100,
y: 300
});
Input = function() {
this.mouseIsDown = false;
this.mouseX = 0;
this.mouseY = 0;
this.offsetX = 0;
this.offsetY = 0;
};
var input = new Input();
document.documentElement.onmousedown = function(ev) {
input.mouseIsDown = true;
};
document.documentElement.onmouseup = function(ev) {
input.mouseIsDown = false;
};
document.documentElement.onmousemove = function(ev) {
ev = ev || window.event;
// input.mouseX = (ev.clientX - workplace.offsetLeft);
// input.mouseY = (ev.clientY - workplace.offsetTop);
input.mouseX = (ev.offsetX);
input.mouseY = (ev.offsetY);
};
function DistanceBetweenPoints(x1, y1, x2, y2) {
return Math.sqrt(((x2 - x1) * (x2 - x1)) + ((y2 - y1) * (y2 - y1)));
}
var canvasDraw = setInterval(function() {
// console.log(input);
if (input.mouseIsDown) {
workplace.style.cursor = "crosshair";
var currentBrushPosition = brush.clone();
currentBrushPosition.setX(input.mouseX);
currentBrushPosition.setY(input.mouseY);
// var distance = DistanceBetweenPoints(brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY());
// if (distance > brush.getRadius() * 2) {
// var fillingLine = new Kinetic.Line({
// points: [brush.getX(), brush.getY(), currentBrushPosition.getX(), currentBrushPosition.getY()],
// stroke: 'yellow',
// strokeWidth: brush.getRadius()*2,
// lineJoin: 'round'
// });
// // layer.add(fillingLine);
// }
layer.add(currentBrushPosition);
brush.remove();
brush = currentBrushPosition;
layer.draw();
// if (fillingLine) {
// fillingLine.remove();
// }
}
if (!input.mouseIsDown) {
workplace.style.cursor = 'default';
}
}, 16);
layer.add(border);
stage.add(layer);
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Coloring Game</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/kineticjs/5.2.0/kinetic.min.js"></script>
</head>
<body>
<div id="main-drawing-window"></div>
<script type="text/javascript" src="./JS files/canvas-draw.js"></script>
</body>
</html>
Don't use individual Kinetic.Circles for each mousemove. Every Kinetic object is a "managed" object and that management takes up a lot of resources. KineticJS will slow to a crawl as the number of circles increases with every mousemove.
Instead, use a Kinetic.Shape and draw you circles onto the canvas with
// This is Pseudo-code since I haven't worked with KineticJS in a while
shapeContext.beginPath();
shapeContext.arc(mouseX,mouseY,20,0,Math.PI*2);
shapeContext.fillStrokeShape(this);
This will probably clear your problem, but if the mouse is moved very far in a single mousemove then you might have to draw a lineTo (instead of arc) between the last mouse point and the current far-away mouse point.

How to Break up large Javascript file with RequireJS

I'm new to Javascript and therefore requireJS - I'm currently teaching myself using Openlayers 3 examples, of which, I've just been appending to one large JS file. Seeing that this is becoming unruly very quickly, I read up about RequireJS and thought I should get into the habit of doing things right from the onset; 'which is where I've hit issues'.
[Not that I imagine it matters, but i'm using Asp.net MVC]
Basically, I wish to break the file up into smaller related modules e.g.
Map [which is used by all modules and initiates the base layer map]
Draw [handles points / polygons etc. and is added to the map as
another layer]
Geolocation [contains geolocation functions for plotting]
etc., etc.
...giving the flexibility to have all layers activated at once, or a select few with easy to manage JS code.
I have had several attempts at breaking this code up into such individual JS files, [map / draw / Geolocation] and all fail as I feel I'm not grasping the requireJS methodology (so as not to confuse readers and myself further, I'm neglecting to add my attempts).
Here is the basic code that works:
require.config({
baseUrl: "/Scripts",
paths: {
//jquery: "/lib/jquery-1.11.1.min",
ol: [
"http://openlayers.org/en/v3.8.1/build/ol",
"/lib/ol"
],
domReady: "/lib/domReady"
},
//map: { main: { test: "/Modules/Test/scripts/test" } },
//The shim section is to tell RequireJS about any dependencies your files have before they can be used.
//Here, we are saying if we call “ol” to load that module, we have to load “jquery” first.
//shim: {
//ol: ["jquery"]
//},
//packages: [
// {
//name: 'test',
//location: 'http://...
//main: 'main'
//}]
});
File I wish to break-up:
define(["ol"], function (ol) {
$(document).ready(function () {
//****************
//------MAP-------
//Setup Map Base
// creating the view
var view = new ol.View({
center: ol.proj.fromLonLat([5.8713, 45.6452]),
zoom: 19
});
// creating the map
var map = new ol.Map({
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
})
],
target: "map",
controls: ol.control.defaults({
attributionOptions: /** #type {olx.control.AttributionOptions} */ ({
collapsible: false
})
}),
view: view
});
//****************
//-----DRAW------
var features = new ol.Collection();
var featureOverlay = new ol.layer.Vector({
source: new ol.source.Vector({ features: features }),
style: new ol.style.Style({
fill: new ol.style.Fill({
color: "rgba(255, 255, 255, 0.2)"
}),
stroke: new ol.style.Stroke({
color: "#ffcc33",
width: 2
}),
image: new ol.style.Circle({
radius: 7,
fill: new ol.style.Fill({
color: "#ffcc33"
})
})
})
});
featureOverlay.setMap(map);
var modify = new ol.interaction.Modify({
features: features,
// the SHIFT key must be pressed to delete vertices, so
// that new vertices can be drawn at the same position
// of existing vertices
deleteCondition: function (event) {
return ol.events.condition.shiftKeyOnly(event) &&
ol.events.condition.singleClick(event);
}
});
map.addInteraction(modify);
var draw; // global so we can remove it later
function addInteraction() {
draw = new ol.interaction.Draw({
features: features,
type: /** #type {ol.geom.GeometryType} */ (typeSelect.value)
});
map.addInteraction(draw);
}
var typeSelect = document.getElementById("type");
/**
* Let user change the geometry type.
* #param {Event} e Change event.
*/
typeSelect.onchange = function (e) {
map.removeInteraction(draw);
addInteraction();
};
addInteraction();
//****************
//---GEOLOCATION---//
// Common app code run on every page can go here
// Geolocation marker
var markerEl = document.getElementById("geolocation_marker");
var marker = new ol.Overlay({
positioning: "center-center",
element: markerEl,
stopEvent: false
});
map.addOverlay(marker);
// LineString to store the different geolocation positions. This LineString
// is time aware.
// The Z dimension is actually used to store the rotation (heading).
var positions = new ol.geom.LineString([],
/** #type {ol.geom.GeometryLayout} */ ("XYZM"));
// Geolocation Control
var geolocation = new ol.Geolocation( /** #type {olx.GeolocationOptions} */({
projection: view.getProjection(),
trackingOptions: {
maximumAge: 10000,
enableHighAccuracy: true,
timeout: 600000
}
}));
var deltaMean = 500; // the geolocation sampling period mean in ms
// Listen to position changes
geolocation.on("change", function (evt) {
var position = geolocation.getPosition();
var accuracy = geolocation.getAccuracy();
var heading = geolocation.getHeading() || 0;
var speed = geolocation.getSpeed() || 0;
var m = Date.now();
addPosition(position, heading, m, speed);
var coords = positions.getCoordinates();
var len = coords.length;
if (len >= 2) {
deltaMean = (coords[len - 1][3] - coords[0][3]) / (len - 1);
}
var html = [
"Position: " + position[0].toFixed(2) + ", " + position[1].toFixed(2),
"Accuracy: " + accuracy,
"Heading: " + Math.round(radToDeg(heading)) + "°",
"Speed: " + (speed * 3.6).toFixed(1) + " km/h",
"Delta: " + Math.round(deltaMean) + "ms"
].join("<br />");
document.getElementById("info").innerHTML = html;
});
geolocation.on("error", function () {
alert("geolocation error");
// FIXME we should remove the coordinates in positions
});
// convert radians to degrees
function radToDeg(rad) {
return rad * 360 / (Math.PI * 2);
}
// convert degrees to radians
function degToRad(deg) {
return deg * Math.PI * 2 / 360;
}
// modulo for negative values
function mod(n) {
return ((n % (2 * Math.PI)) + (2 * Math.PI)) % (2 * Math.PI);
}
function addPosition(position, heading, m, speed) {
var x = position[0];
var y = position[1];
var fCoords = positions.getCoordinates();
var previous = fCoords[fCoords.length - 1];
var prevHeading = previous && previous[2];
if (prevHeading) {
var headingDiff = heading - mod(prevHeading);
// force the rotation change to be less than 180°
if (Math.abs(headingDiff) > Math.PI) {
var sign = (headingDiff >= 0) ? 1 : -1;
headingDiff = -sign * (2 * Math.PI - Math.abs(headingDiff));
}
heading = prevHeading + headingDiff;
}
positions.appendCoordinate([x, y, heading, m]);
// only keep the 20 last coordinates
positions.setCoordinates(positions.getCoordinates().slice(-20));
// FIXME use speed instead
if (heading && speed) {
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //"data/geolocation_marker_heading.png";F:\DeleteMeThree\_Orchard-19x\src\Orchard.Web\Modules\Cns.OL\Contents/Images/geolocation_marker.png
} else {
//alert(markerEl.src); PETE: Not sure if this is std OL practice, but this is achieved by already having an element
//called "geolocation_marker" in the dom as an img, which this uses? Strange to me
markerEl.src = "/OrchardLocal/Media/Default/Map/geolocation_marker.png"; //I added img via media module - ridiculous?!
}
}
var previousM = 0;
// change center and rotation before render
map.beforeRender(function (map, frameState) {
if (frameState !== null) {
// use sampling period to get a smooth transition
var m = frameState.time - deltaMean * 1.5;
m = Math.max(m, previousM);
previousM = m;
// interpolate position along positions LineString
var c = positions.getCoordinateAtM(m, true);
var view = frameState.viewState;
if (c) {
view.center = getCenterWithHeading(c, -c[2], view.resolution);
view.rotation = -c[2];
marker.setPosition(c);
}
}
return true; // Force animation to continue
});
// recenters the view by putting the given coordinates at 3/4 from the top or
// the screen
function getCenterWithHeading(position, rotation, resolution) {
var size = map.getSize();
var height = size[1];
return [
position[0] - Math.sin(rotation) * height * resolution * 1 / 4,
position[1] + Math.cos(rotation) * height * resolution * 1 / 4
];
}
// postcompose callback
function render() {
map.render();
}
//EMP
//$("#geolocate").click(function () {
// alert("JQuery Running!");
//});
// geolocate device
var geolocateBtn = document.getElementById("geolocate");
geolocateBtn.addEventListener("click", function () {
geolocation.setTracking(true); // Start position tracking
map.on("postcompose", render);
map.render();
disableButtons();
}, false);
});
})
Considering that i'll have many more modules to attach in the future, what would be the best way to break-up this code using RequireJS for efficiency and coding functionality / maintenance.
Thanks ever so much for your guidance / thoughts, cheers WL
Every require module (defined using define) is supposed to return a function/object. The breakup shown in question doesn't, instead just splits the code. Think of some hypothetical module buckets and put each piece of code (or function) into a module. Then group the code into a require js module and return the module's interface.
Let me try to explain further with an example.
main.js
$(document).ready(function(){
$("#heyINeedMap").click(function(){
require(['map'],function(Map){
Map.render($target);
});
});
});
OR
$(document).ready(function(){
require(['map','geolocation'],function(Map,Geolocation){
window.App.start = true;
window.App.map = Map; //won't suggest, but you can do.
window.App.geolocation = Geolocation;
//do something.
$("#lastCoords").click(function(){
var coords = App.geolocation.getLastSavedCoords();
if(!!coords){
coords = App.geolocation.fetchCurrentCoords();
}
alert(coords);
});
});
});
map.js
define(['jquery'],function($){
var privateVariableAvailableToAllMapInstances = 'something';
var mapType = 'scatter';
return function(){
render: function(el){
//rendering logic goes here
},
doSomethingElse: function(){
privateVariable = 'some new value';
//other logic goes here
},
changeMapType: function(newType){
mapType = newType;
//...
}
}
});
geolocation.js
//Just assuming that it needs jquery & another module called navigation to work.
define(['jquery','navigation'], function($,Gnav){
return {
var coordinates = Gnav.lastSavedCoords;
fetchCurrentCoords: function(){
//coordinates = [79.12213, 172.12342]; //fetch from API/something
return coordinates;
},
getLastSavedCoords: function(){
return coordinates;
}
}
});
Hope this gives an idea on how to proceed.

Prototype animation in Kinetic JS

I would like to make a "prototype" of animations for a future game. But I'm totally a noob in kineticJS.
I have an object where I make all my functions:
var app = {}
I have a function init to build a layer, a stage and declare that I will use requestAnimationFrame:
init: function(){
layer = new Kinetic.Layer();
DrawingTab = [];
stage = new Kinetic.Stage({
container: 'canvasDemo',
width: 800,
height: 600
});
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback){
window.setTimeout(callback, 1000 / 60);
};
})();
}
Secondly, I've got one function to build my rects:
createObject: function(){
rect = new Kinetic.Rect({
x: 50,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur',
id: 'batteur'
});
rect1 = new Kinetic.Rect({
x: 300,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur1',
id: 'batteur1'
});
rect2 = new Kinetic.Rect({
x: 550,
y: 50,
width: 150,
height: 150,
fill: 'black',
name: 'batteur2',
id: 'batteur2'
});
layer.add(rect);
layer.add(rect1);
layer.add(rect2);
stage.add(layer);
DrawingTab.push(rect,rect1,rect2,rect3,rect4,rect5);
}
That's all I did. And then, I want to know how to animate like that:
every 20 secondes, one of the rect (select randomly) change of color,
and the user have to click on it.
the user have 5sec to click on it, and if he doesn't click, the rect change to the beginning color.
I hope explanations are clear and something will can help me, because I'm totally lost.
You should use Kinetic.Animation for animations because it optimizes redraws. Here's an example
If your game is using sprites, you should be using the Sprite shape. Here's an example of that
You don't need requestAnimationFrame or Kinetic.Animation to handle this, considering the kind of animation you want. Only use animations if you need to change the animation status every frame.
See this working DEMO.
Using setInterval and setTimeout the application became more performant.
I reduce the time of change of color to 5 seconds and the time to click to 2 seconds, just to quickly visualization of the features.
Here is the code added:
// times (make changes according)
var timeToChange = 5000; // 5 seconds
var timeToClick = 2000; // 2 seconds
// render all rects
layer.drawScene();
// add a logical rect for each rect in DrawingTab
var LogicalTab = [];
for (var i = 0; i < DrawingTab.length; ++i) {
LogicalTab.push({
isPressed: false,
frame: 0
});
}
// return a random integer between (min, max)
function random(min, max) {
return Math.round(Math.random() * (max - min) + min);
};
// define colors
var colors = ["red", "green", "blue"];
// reset state of current rect
function reset(n) {
var drect = DrawingTab[n];
var lrect = LogicalTab[n];
// check if current rect was clicked
setTimeout(function () {
if (!lrect.isPressed) {
drect.setFill("black");
// redraw scene
layer.drawScene();
lrect.frame = 0;
}
// turn off click event
drect.off("click");
}, timeToClick);
}
// start the animation
var start = setInterval(function () {
// select a rect randomly
var rand = random(0, 2);
var drect = DrawingTab[rand];
var lrect = LogicalTab[rand];
// change color
drect.setFill(colors[lrect.frame]);
// redraw scene
layer.drawScene();
// flag that current rect is not clicked
lrect.isPressed = false;
// check for click events
drect.on("click", function () {
// flag that current rect is clicked
lrect.isPressed = true;
// hold current color
lrect.frame++;
lrect.frame = lrect.frame % colors.length;
});
// reset current rect (only if it is not clicked)
reset(rand);
}, timeToChange);
I'm a newbye here, but I hope I'm able to help. KineticJS don't need requestAnimationFrame, because it has already something that handles animations. so first of all I think you should have a look to this page
if you want to make the rect's color change every 20 s, you may do something like this:
var anim = new Kinetic.Animation(function(frame) {
if(frame.time > 20000)
{
frame.time = 0;
colors = ['red', 'blue', 'violet'];
ora = colors[Math.floor(Math.random()*3)];
DrawingTab[Math.floor(Math.random*6)].setAttrs({fill: ora});
}
},layer);
then, for the 5sec stuff, I tried to write something
var currentRect = { value:0, hasClicked : true };
var anim2 = new Kinetic.Animation(function(frame) {
if(frame.time > 20000)
{
frame.time = 0;
colors = ['red', 'lightblue', 'violet'];
ora = colors[Math.floor(Math.random()*3)];
currentRect.hasClicked = false;
currentRect.value=Math.floor(Math.random()*6);
DrawingTab[currentRect.value].setAttrs({fill: ora});
}
if (!currentRect.hasClicked && frame.time>5000)
{
DrawingTab[currentRect.value].setAttrs({fill: 'black'});
currentRect.hasClicked = true;
}
DrawingTab[currentRect.value].on('click',function(){ if (frame.time<=5000) currentRect.hasClicked = true;});
},layer);
anim2.start();
I've just tried something similiar and it looks like it's working :)
p.s. sorry about my english, I'm only a poor italian student
p.p.s. I'm sure the code can be optimized, but for now I think it can be alright

HTML5 canvas continuously stroking lines

I want to draw some continuously growing lines in HTML5 and Javascript. Here is what I want to do:
A point located at the center of my screen will have 3 lines growing (120 degree to each other) to a certain length, say 50 pix, then each of this 3 vertex will become a new center and have another 3 lines.
(I couldnt post images due to low reputation I have, hopefully you know what I mean abt the image here...)
I already written the function to have a array of all the points I need as the centers, starting from the center of my screen. I am thinking to write a loop over this array to draw the lines. I DO NOT want to directly use the stroke so that the line just appears on the screen. I want to have something like the the lines are drawn bit by bit (bad english here, please excuse my english) until it reaches the pre-defined length. However my code dont work quite well here, it only displays all the center points and only the last center point has the movement to have the 3 lines to grow...
I need to know the correct way to do this... many thanks in advance!
(please ignore the variable time or startTime in my code... )
<script>
window.requestAnimFrame = (function(callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
var canvas = document.getElementById('myCanvas');
canvas.width= window.innerWidth;
canvas.height= window.innerHeight;
var context = canvas.getContext('2d');
var totalLength = 50;
var centreSet = new Array();
var counter = 0;
var centre = {
x: canvas.width / 2,
y: canvas.height / 2,
};
var myLine = {
length : 0,
color : 'grey',
lineWidth : 0.5,
};
function drawLine(centre, context, mylength) {
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - mylength);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * mylength, centre.y + mylength/2);
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * mylength, centre.y + mylength/2);
context.lineWidth = myLine.lineWidth;
context.strokeStyle = myLine.color;
context.closePath();
context.stroke();
}
function startAnimate(centre, canvas, context, startTime, mylength) {
// update
var time = (new Date()).getTime() - startTime;
var linearSpeed = 5;
// pixels / second
var newX = linearSpeed / 10;
if(mylength < totalLength) {
mylength = mylength + newX;
// clear
//context.clearRect(0, 0, canvas.width, canvas.height);
drawLine(centre, context, mylength);
// request new frame
requestAnimFrame(function() {
startAnimate(centre, canvas, context, startTime, mylength);
});
}
}
function animate(centre, canvas, context, startTime){
//create array to have all the center points
centreSet = getCentres();
for (var i = 0; i < centreSet.length; i++){
//pass the x and y values in a object for each center we have in the array
centre.x = str2x(centreSet[i]);
centre.y = str2y(centreSet[i]);
startAnimate(centre, canvas, context, startTime, 0);
}
}
setTimeout(function() {
var startTime = (new Date()).getTime();
animate(centre, canvas, context, startTime);
}, 1000);
I just edited your code, I added the following part:
var length = 0;
for(var i = 0; i < 380; i++){
window.setTimeout(function() {drawFrame(length);},16.67);
length = length + 0.25;
}
I expect the screen appears to draw the incremental lines bit by bit until it reaches the length I want. However, it seems like the whole incremental process is not shown and it only shows the finished drawing.
Can anyone tell me why?
Regarding your followup question about why your animation loop fails
By putting your setTimeout in a for-loop, each new setTimeout is cancelling the previous setTimeout.
So you’re just left with the very last setTimeout running to completion.
In an animation loop, you typically do 3 things during each "frame":
Change some data to reflect how the new frame is different from the previous frame.
Draw the frame.
Test if the animation is complete. If not, do another frame (go to #1).
The setTimeout function is used to do the last part of #3 (do another frame)
So setTimeout is really acting as your animation loop. --- Your for-loop is not needed.
This is how you would restructure your code to follow this pattern:
var length=0;
var maxLength=50;
function draw(){
// make the line .25 longer
length=length+.25;
// draw
drawFrame(length);
// test if the line is fully extended
// if not, call setTimeout again
// setTimeout(draw,100) will call this same draw() function in 100ms
if(length<maxLength){
setTimeout(draw,100);
}
}
[Edited: to include spawning of child objects after lines reach terminal distance]
In your code you were not spawning new center points when the lines reached their maximum extension.
I would suggest that each of your centre objects have at least this much information in order to spawn a new set of centre objects when their lines reach terminal length:
var newCentrePoint={
x:x,
y:y,
maxLength:newMaxLength,
growLength:growLength,
currentLength:0,
isActive:true
}
The x,y are the centerpoint’s coordinates.
maxLength is the maximum extension of the 3 lines before they are terminated.
growLength is the amount by which each line will grow in each new frame.
currentLength is the current length of the line.
isActive is a flag indicating if this point is growing lines (true) or if it’s terminated (false)
Then when each line reaches terminal length you can spawn a new set of lines like this:
// spawns 3 new centre points – default values are for testing
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
// new center points are spawned at the termination points of the 3 current lines
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
}
// creates a new point object and puts in the centreSet array for processing
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/Vc8Gf/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var context = canvas.getContext('2d');
// colors
var colors=["red","blue","gold","purple","green"];
var colorIndex=0;
//
var centreSet=[]
var generations=1;
// seed point
newPoint(canvas.width/2,canvas.height/2,100,"red",15);
// start
draw();
//
function draw(){
//
context.clearRect(0,0,canvas.width,canvas.height);
//
for(var i=0;i<centreSet.length;i++){
//
var centre=centreSet[i];
//
if(centre.isActive){
//
centre.currentLength+=centre.growLength;
//
if(centre.currentLength>=centre.maxLength){
centre.isActive=false;
centre.currentLength=centre.maxLength;
spawn(centre);
}
}
//
drawLines(centre);
}
//
if(generations<120){
setTimeout(draw,500);
}else{
context.font="18pt Verdana";
context.fillText("Finished 120 generations",40,350);
}
}
function spawn(point,newMaxLength,newColor,growLength,newLineWidth){
var max=newMaxLength||point.maxLength/2;
var color=newColor|| (colors[++colorIndex%(colors.length)]);
var grow=growLength||point.growLength/2;
var lw=newLineWidth||point.lineWidth-1;
newPoint((point.x),(point.y-point.maxLength),max,color,grow,lw);
newPoint((point.x-0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
newPoint((point.x+0.866*point.maxLength),(point.y+point.maxLength/2),max,color,grow,lw);
generations++;
}
function newPoint(x,y,newMaxLength,newColor,growLength,newLineWidth){
var newPt={
x:x,
y:y,
maxLength:newMaxLength,
color:newColor,
lineWidth:newLineWidth,
growLength:growLength,
currentLength:0,
isActive:true
}
centreSet.push(newPt);
}
function drawLines(centre) {
var length=centre.currentLength;
//
context.beginPath();
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x, centre.y - length);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x - 0.866 * length, centre.y + length/2);
//
context.moveTo(centre.x, centre.y);
context.lineTo(centre.x + 0.866 * length, centre.y + length/2);
//
context.strokeStyle=centre.color;
context.lineWidth = centre.lineWidth;
context.stroke();
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=400 height=400></canvas>
</body>
</html>

Categories