I am trying to create a draggable marker that is confined to a polyline. I have read this post (Confine dragging of Google Maps V3 Marker to Polyline), but I do not want to create the points that the marker can move along. Are there other ways to do this without having to create a points array for the marker? If anyone can point me in the right direction, it is much appreciated.
From what I understand, you have to load the polyline points into an array. It seems that there is no way around this. I am not sure how the the directions api snaps to roads, but I am assuming that it is based on this concept (loading points into an array).
I have found an older maps v2 library that updates the marker based on mouse movement events, which loads the line data on zoom end. I have updated the code to work with api v3 and replaced the mouse events with drag events.
To use this library, initialize like this:
var snapToRoute = new SnapToRoute(map_instance, initial_marker, polyline);
The library can be found here: SnapToRoute
** Update ** example fiddle
Here is my modified version:
function SnapToRoute(map, marker, polyline) {
this.routePixels_ = [];
this.normalProj_ = map.getProjection();
this.map_ = map;
this.marker_ = marker;
this.polyline_ = polyline;
this.init_();
}
SnapToRoute.prototype.init_ = function () {
this.loadLineData_();
this.loadMapListener_();
};
SnapToRoute.prototype.updateTargets = function (marker, polyline) {
this.marker_ = marker || this.marker_;
this.polyline_ = polyline || this.polyline_;
this.loadLineData_();
};
SnapToRoute.prototype.loadMapListener_ = function () {
var me = this;
google.maps.event.addListener(me.marker_, "dragend", function (evt) {
me.updateMarkerLocation_(evt.latLng);
});
google.maps.event.addListener(me.marker_, "drag", function (evt) {
me.updateMarkerLocation_(evt.latLng);
});
google.maps.event.addListener(me.map_, "zoomend", function (evt) {
me.loadLineData_();
});
};
SnapToRoute.prototype.loadLineData_ = function () {
var zoom = this.map_.getZoom();
this.routePixels_ = [];
var path = this.polyline_.getPath();
for (var i = 0; i < path.getLength() ; i++) {
var Px = this.normalProj_.fromLatLngToPoint(path.getAt(i));
this.routePixels_.push(Px);
}
};
SnapToRoute.prototype.updateMarkerLocation_ = function (mouseLatLng) {
var markerLatLng = this.getClosestLatLng(mouseLatLng);
this.marker_.setPosition(markerLatLng);
};
SnapToRoute.prototype.getClosestLatLng = function (latlng) {
var r = this.distanceToLines_(latlng);
return this.normalProj_.fromPointToLatLng(new google.maps.Point(r.x, r.y));
};
SnapToRoute.prototype.getDistAlongRoute = function (latlng) {
if (typeof (opt_latlng) === 'undefined') {
latlng = this.marker_.getLatLng();
}
var r = this.distanceToLines_(latlng);
return this.getDistToLine_(r.i, r.to);
};
SnapToRoute.prototype.distanceToLines_ = function (mouseLatLng) {
var zoom = this.map_.getZoom();
var mousePx = this.normalProj_.fromLatLngToPoint(mouseLatLng);
var routePixels_ = this.routePixels_;
return this.getClosestPointOnLines_(mousePx, routePixels_);
};
SnapToRoute.prototype.getDistToLine_ = function (line, to) {
var routeOverlay = this.polyline_;
var d = 0;
for (var n = 1; n < line; n++) {
d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(n - 1), routeOverlay.getAt(n));
}
d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(line - 1), routeOverlay.getAt(line)) * to;
return d;
};
SnapToRoute.prototype.getClosestPointOnLines_ = function (pXy, aXys) {
var minDist;
var to;
var from;
var x;
var y;
var i;
var dist;
if (aXys.length > 1) {
for (var n = 1; n < aXys.length ; n++) {
if (aXys[n].x !== aXys[n - 1].x) {
var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x);
var b = aXys[n].y - a * aXys[n].x;
dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1);
} else {
dist = Math.abs(pXy.x - aXys[n].x);
}
var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2);
var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2);
var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2);
var dist2 = Math.pow(dist, 2);
var calcrl2 = ln2 - dist2 + lnm12 - dist2;
if (calcrl2 > rl2) {
dist = Math.sqrt(Math.min(ln2, lnm12));
}
if ((minDist == null) || (minDist > dist)) {
to = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
from = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
minDist = dist;
i = n;
}
}
if (to > 1) {
to = 1;
}
if (from > 1) {
to = 0;
from = 1;
}
var dx = aXys[i - 1].x - aXys[i].x;
var dy = aXys[i - 1].y - aXys[i].y;
x = aXys[i - 1].x - (dx * to);
y = aXys[i - 1].y - (dy * to);
}
return { 'x': x, 'y': y, 'i': i, 'to': to, 'from': from };
};
example fiddle
code snippet:
var geocoder;
var directionsDisplay;
var directionsService = new google.maps.DirectionsService();
var map;
var polyline = new google.maps.Polyline({
path: [],
strokeColor: '#FF0000',
strokeWeight: 3
});
var marker;
function initialize() {
directionsDisplay = new google.maps.DirectionsRenderer();
map = new google.maps.Map(
document.getElementById("map_canvas"), {
center: new google.maps.LatLng(37.4419, -122.1419),
zoom: 13,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
calcRoute("New York, NY", "Baltimore, MD");
directionsDisplay.setMap(map);
}
google.maps.event.addDomListener(window, "load", initialize);
function calcRoute(start, end) {
var request = {
origin: start,
destination: end,
travelMode: google.maps.TravelMode.DRIVING
};
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
// directionsDisplay.setDirections(response);
renderRoute(response);
}
});
}
function renderRoute(response) {
var bounds = new google.maps.LatLngBounds();
var route = response.routes[0];
var summaryPanel = document.getElementById("directions_panel");
var detailsPanel = document.getElementById("direction_details");
var path = response.routes[0].overview_path;
var legs = response.routes[0].legs;
for (i = 0; i < legs.length; i++) {
if (i == 0) {
marker = new google.maps.Marker({
position: legs[i].start_location,
draggable: true,
map: map
});
}
var steps = legs[i].steps;
for (j = 0; j < steps.length; j++) {
var nextSegment = steps[j].path;
for (k = 0; k < nextSegment.length; k++) {
polyline.getPath().push(nextSegment[k]);
bounds.extend(nextSegment[k]);
}
}
}
polyline.setMap(map);
map.fitBounds(bounds);
var snapToRoute = new SnapToRoute(map, marker, polyline);
}
function SnapToRoute(map, marker, polyline) {
this.routePixels_ = [];
this.normalProj_ = map.getProjection();
this.map_ = map;
this.marker_ = marker;
this.editable_ = Boolean(false);
this.polyline_ = polyline;
this.init_();
}
SnapToRoute.prototype.init_ = function() {
this.loadLineData_();
this.loadMapListener_();
};
SnapToRoute.prototype.updateTargets = function(marker, polyline) {
this.marker_ = marker || this.marker_;
this.polyline_ = polyline || this.polyline_;
this.loadLineData_();
};
SnapToRoute.prototype.loadMapListener_ = function() {
var me = this;
google.maps.event.addListener(me.marker_, "dragend", function(evt) {
me.updateMarkerLocation_(evt.latLng);
});
google.maps.event.addListener(me.marker_, "drag", function(evt) {
me.updateMarkerLocation_(evt.latLng);
});
google.maps.event.addListener(me.map_, "zoomend", function(evt) {
me.loadLineData_();
});
};
SnapToRoute.prototype.loadLineData_ = function() {
var zoom = this.map_.getZoom();
this.routePixels_ = [];
var path = this.polyline_.getPath();
for (var i = 0; i < path.getLength(); i++) {
var Px = this.normalProj_.fromLatLngToPoint(path.getAt(i));
this.routePixels_.push(Px);
}
};
SnapToRoute.prototype.updateMarkerLocation_ = function(mouseLatLng) {
var markerLatLng = this.getClosestLatLng(mouseLatLng);
this.marker_.setPosition(markerLatLng);
};
SnapToRoute.prototype.getClosestLatLng = function(latlng) {
var r = this.distanceToLines_(latlng);
return this.normalProj_.fromPointToLatLng(new google.maps.Point(r.x, r.y));
};
SnapToRoute.prototype.getDistAlongRoute = function(latlng) {
if (typeof(opt_latlng) === 'undefined') {
latlng = this.marker_.getLatLng();
}
var r = this.distanceToLines_(latlng);
return this.getDistToLine_(r.i, r.to);
};
SnapToRoute.prototype.distanceToLines_ = function(mouseLatLng) {
var zoom = this.map_.getZoom();
var mousePx = this.normalProj_.fromLatLngToPoint(mouseLatLng);
var routePixels_ = this.routePixels_;
return this.getClosestPointOnLines_(mousePx, routePixels_);
};
SnapToRoute.prototype.getDistToLine_ = function(line, to) {
var routeOverlay = this.polyline_;
var d = 0;
for (var n = 1; n < line; n++) {
d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(n - 1), routeOverlay.getAt(n));
}
d += google.maps.geometry.spherical.computeDistanceBetween(routeOverlay.getAt(line - 1), routeOverlay.getAt(line)) * to;
return d;
};
SnapToRoute.prototype.getClosestPointOnLines_ = function(pXy, aXys) {
var minDist;
var to;
var from;
var x;
var y;
var i;
var dist;
if (aXys.length > 1) {
for (var n = 1; n < aXys.length; n++) {
if (aXys[n].x !== aXys[n - 1].x) {
var a = (aXys[n].y - aXys[n - 1].y) / (aXys[n].x - aXys[n - 1].x);
var b = aXys[n].y - a * aXys[n].x;
dist = Math.abs(a * pXy.x + b - pXy.y) / Math.sqrt(a * a + 1);
} else {
dist = Math.abs(pXy.x - aXys[n].x);
}
var rl2 = Math.pow(aXys[n].y - aXys[n - 1].y, 2) + Math.pow(aXys[n].x - aXys[n - 1].x, 2);
var ln2 = Math.pow(aXys[n].y - pXy.y, 2) + Math.pow(aXys[n].x - pXy.x, 2);
var lnm12 = Math.pow(aXys[n - 1].y - pXy.y, 2) + Math.pow(aXys[n - 1].x - pXy.x, 2);
var dist2 = Math.pow(dist, 2);
var calcrl2 = ln2 - dist2 + lnm12 - dist2;
if (calcrl2 > rl2) {
dist = Math.sqrt(Math.min(ln2, lnm12));
}
if ((minDist == null) || (minDist > dist)) {
to = Math.sqrt(lnm12 - dist2) / Math.sqrt(rl2);
from = Math.sqrt(ln2 - dist2) / Math.sqrt(rl2);
minDist = dist;
i = n;
}
}
if (to > 1) {
to = 1;
}
if (from > 1) {
to = 0;
from = 1;
}
var dx = aXys[i - 1].x - aXys[i].x;
var dy = aXys[i - 1].y - aXys[i].y;
x = aXys[i - 1].x - (dx * to);
y = aXys[i - 1].y - (dy * to);
}
return {
'x': x,
'y': y,
'i': i,
'to': to,
'from': from
};
};
html,
body,
#map_canvas {
height: 100%;
width: 100%;
margin: 0px;
padding: 0px
}
<script src="https://maps.googleapis.com/maps/api/js"></script>
<div id="map_canvas" style="border: 2px solid #3872ac;"></div>
Related
I have a large data to plot and by doing this the browser hang so I found a solution in V3 to avoid this effect, the problem is, I need it in D3V4 or I need to brush and zoom in V3.
I'm trying to rewrite the following code in D3 V4, it seems as if it cracks by updateSelection. Did I miss something?
my code:
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
var height = 500;
var width = 960;
var timer, startTime;
var startTime;
var BATCH_SIZE = 1000;
function showTimeSince(startTime) {
var currentTime = new Date().getTime();
var runtime = currentTime - startTime;
document.getElementById('timeRendering').innerHTML = runtime + 'ms';
}
function startTimer() {
stopTimer();
startTime = new Date().getTime();
timer = setInterval(function() {
showTimeSince(startTime);
}, 10);
showTimeSince(startTime);
}
function stopTimer() {
if (timer) {
clearInterval(timer);
}
showTimeSince(startTime);
}
function drawCircles(svg, data, batchSize) {
startTimer();
var circles = svg.selectAll('circle').data(data);
function drawBatch(batchNumber) {
return function() {
var startIndex = batchNumber * batchSize;
var stopIndex = Math.min(data.length, startIndex + batchSize);
var updateSelection = d3.selectAll(circles[0].slice(startIndex, stopIndex));
var enterSelection = d3.selectAll(circles.enter()[0].slice(startIndex, stopIndex));
var exitSelection = d3.selectAll(circles.exit()[0].slice(startIndex, stopIndex));
enterSelection.each(function(d, i) {
var newElement = svg.append('circle')[0][0];
enterSelection[0][i] = newElement;
updateSelection[0][i] = newElement;
newElement.__data__ = this.__data__;
}).attr('r', 3);
exitSelection.remove();
updateSelection
.attr('cx', function(d) { return d.x })
.attr('cy', function(d) { return d.y });
if (stopIndex >= data.length) {
stopTimer();
} else {
setTimeout(drawBatch(batchNumber + 1), 0);
}
};
}
setTimeout(drawBatch(0), 0);
}
function renderChart() {
var numPoints = parseInt(document.getElementsByName('numPoints')[0].value, 10);
if (isNaN(numPoints)) {
return;
}
startTimer();
var data = [];
for (var i = 0; i < numPoints; i++) {
data.push({
x: Math.random() * width,
y: Math.random() * height
});
}
var svg = d3.selectAll('svg').data([0]);
svg.enter().append('svg')
.attr("height", height)
.attr("width", width);
drawCircles(svg, data, BATCH_SIZE);
}
</script>
</head>
<body>
<form action="">
<input name="numPoints" type="text" value="10000">
<button type="button" id="render" onClick="renderChart()">Render</button>
</form>
Time Rendering: <span id="timeRendering"></span>
</body>
</html>
Trying to get series values in the selected area after mouseup.
Need to calculate width and height of the selected are in the chart with respect to axis.
[`https://jsbin.com/wapovohiwe/edit?html,output`][1]
I modified your example to draw the rectangle and to output the coordinates of the
Chart1 = new Tee.Chart("canvas");
Chart1.legend.visible = false;
Chart1.panel.format.gradient.visible = false;
Chart1.panel.format.fill = "white";
Chart1.walls.back.format.fill = "white";
Chart1.addSeries(new Tee.Line([1, 3, 0, 2, 7, 5, 6]));
Chart1.zoom.enabled = false;
var myFormat = new Tee.Format(Chart1);
myFormat.transparency = 0.9;
var rect = {},
drag = false;
Chart1.mousedown = function(e) {
rect.startX = e.x - canvas.offsetLeft - canvas.parentElement.offsetLeft - canvas.parentElement.parentElement.offsetLeft;
rect.startY = e.y - canvas.offsetTop - canvas.parentElement.offsetTop - canvas.parentElement.parentElement.offsetTop;
drag = true;
}
Chart1.mouseup = function(p) {
var i, tmpP = {};
points = [];
var s = Chart1.series.items[0];
for (i = 0; i < s.count(); i++) {
s.calc(i, tmpP);
if ((tmpP.x >= rect.startX) && (tmpP.x <= rect.startX + rect.w) && (tmpP.y >= rect.startY) && (tmpP.y <= rect.startY + rect.h))
console.log("index: " + i + ", value: " + s.data.values[i]);
}
drag = false;
}
Chart1.mousemove = function(e) {
if (drag) {
rect.w = e.x - rect.startX;
rect.h = e.y - rect.startY;
drawChart();
}
}
function drawChart() {
Chart1.draw();
myFormat.rectangle(rect.startX, rect.startY, rect.w, rect.h);
}
Chart1.draw();
<script src="https://www.steema.com/files/public/teechart/html5/latest/src/teechart.js"></script>
<canvas id="canvas" height="400" width="700"></canvas>
On a map of the USA, I have been asked to draw 50,000 circles each with a 5000-yard radius.
The lat-lon locations are scattered throughout the country, but a large number of these circles overlap. Opacity is set to 0.05; regions with many superimposed circles become more opaque.
The circles start to appear, but after about 30 seconds Chrome crashes, displaying the "He's dead, Jim" message.
About the error message:
According to Error: "He's Dead, Jim!":
You might see the “He’s Dead, Jim!” message in a tab if:
You don’t have enough memory available to run the tab. Computers rely on memory to run apps, extensions, and programs. Low memory
can cause them to run slowly or stop working.
You stopped a process using Google Chrome's Task Manager, the system's task manager, or a command line tool.
It evidently occurs since you are trying to render 50k objects. In order to render such amount of objects I would recommend to consider Overlay approach. In that case the performance could be improved considerably since city icons could ve rendered via canvas element instead of div ones.
Having said that, the below example demonstrates how to render multiple amount of cities (1000 cities ) using the described approach:
var overlay;
USCitiesOverlay.prototype = new google.maps.OverlayView();
function USCitiesOverlay(map) {
this._map = map;
this._cities = [];
this._radius = 6;
this._container = document.createElement("div");
this._container.id = "citieslayer";
this.setMap(map);
this.addCity = function (lat, lng) {
this._cities.push({position: new google.maps.LatLng(lat,lng)});
};
}
USCitiesOverlay.prototype.createCityIcon = function (id,pos) {
/*var cityIcon = document.createElement('div');
cityIcon.setAttribute('id', "cityicon_" + id);
cityIcon.style.position = 'absolute';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.width = cityIcon.style.height = (this._radius * 2) + 'px';
cityIcon.className = "circleBase city";
return cityIcon;*/
var cityIcon = document.createElement('canvas');
cityIcon.id = 'cityicon_' + id;
cityIcon.width = cityIcon.height = this._radius * 2;
cityIcon.style.width = cityIcon.width + 'px';
cityIcon.style.height = cityIcon.height + 'px';
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
cityIcon.style.position = "absolute";
var centerX = cityIcon.width / 2;
var centerY = cityIcon.height / 2;
var ctx = cityIcon.getContext('2d');
ctx.fillStyle = 'rgba(160,16,0,0.6)';
ctx.beginPath();
ctx.arc(centerX, centerY, this._radius, 0, Math.PI * 2, true);
ctx.fill();
return cityIcon;
};
USCitiesOverlay.prototype.ensureCityIcon = function (id,pos) {
var cityIcon = document.getElementById("cityicon_" + id);
if(cityIcon){
cityIcon.style.left = (pos.x - this._radius) + 'px';
cityIcon.style.top = (pos.y - this._radius) + 'px';
return cityIcon;
}
return this.createCityIcon(id,pos);
};
USCitiesOverlay.prototype.onAdd = function () {
var panes = this.getPanes();
panes.overlayLayer.appendChild(this._container);
};
USCitiesOverlay.prototype.draw = function () {
var zoom = this._map.getZoom();
var overlayProjection = this.getProjection();
var container = this._container;
this._cities.forEach(function(city,idx){
var xy = overlayProjection.fromLatLngToDivPixel(city.position);
var cityIcon = overlay.ensureCityIcon(idx,xy);
container.appendChild(cityIcon);
});
};
USCitiesOverlay.prototype.onRemove = function () {
this._container.parentNode.removeChild(this._container);
this._container = null;
};
function getRandomInterval(min, max) {
return Math.random() * (max - min) + min;
}
function generateCityMap(count) {
var citymap = [];
var minPos = new google.maps.LatLng(49.25, -123.1);
var maxPos = new google.maps.LatLng(34.052234, -74.005973);
for(var i = 0; i < count;i++)
{
var lat = getRandomInterval(minPos.lat(),maxPos.lat());
var lng = getRandomInterval(minPos.lng(),maxPos.lng());
var population = getRandomInterval(10000,1000000);
citymap.push({
location: new google.maps.LatLng(lat, lng),
population: population
});
}
return citymap;
}
function initialize() {
var mapOptions = {
zoom: 4,
center: new google.maps.LatLng(37.09024, -95.712891),
mapTypeId: google.maps.MapTypeId.TERRAIN
};
var map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
overlay = new USCitiesOverlay(map);
overlay.addCity(40.714352, -74.005973); //chicago
overlay.addCity(40.714352, -74.005973); //newyork
overlay.addCity(34.052234, -118.243684); //losangeles
overlay.addCity(49.25, -123.1); //vancouver
var citymap = generateCityMap(1000);
citymap.forEach(function(city){
overlay.addCity(city.location.lat(), city.location.lng());
});
}
google.maps.event.addDomListener(window, 'load', initialize);
html, body, #map-canvas {
height: 100%;
margin: 0px;
padding: 0px;
}
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp&signed_in=true"></script>
<div id="map-canvas"></div>
I've got a piece of code here that plots a group of paths on a map. I'm trying to get the map to zoom and centre so all the paths are visible, but I can't get anything to work. Here's what the code looks like before I started:
var points = new Array();
var pointsData = [];
var pointsData = $('#master-' + r + ' > #route-data').html();
if (pointsData != '')
{
var pointsArray = JSON.parse(pointsData);
for (var p=0; p<pointsArray.length; p++) {
points.push(new google.maps.LatLng(pointsArray[p][0], pointsArray[p][1]));
}
$.OverMap.drawRoute(points);
}
else $.OverMap.drawDirections({preserveViewport:false});
And after fiddling with it:
map = this.map.map;
map.fitBounds(this.map.bounds);
zoomChangeBoundsListener =
google.maps.event.addListenerOnce(map, 'bounds_changed', function(event) {
if (this.getZoom()){
this.setZoom(16);
}
});
setTimeout(function(){google.maps.event.removeListener(zoomChangeBoundsListener)}, 2000);
// Draw route
var bounds = new google.maps.LatLngBounds();
var points = new Array();
var pointsData = [];
var pointsData = $('#master-' + r + ' > #route-data').html();
if (pointsData != '')
{
var pointsArray = JSON.parse(pointsData);
for (var p=0; p<pointsArray.length; p++) {
points.push(new google.maps.LatLng(pointsArray[p][0], pointsArray[p][1]));
bounds.extend(pointsArray[p][0], pointsArray[p][1]);
}
$.OverMap.drawRoute(points);
}
else $.OverMap.drawDirections({preserveViewport:false});
Can anyone point out where I'm going wrong?
the google.maps.LatLngBounds.extend takes a google.maps.LatLng object as an argument.
not:
for (var p=0; p<pointsArray.length; p++) {
points.push(new google.maps.LatLng(pointsArray[p][0], pointsArray[p][1]));
bounds.extend(pointsArray[p][0], pointsArray[p][1]);
}
instead do:
for (var p=0; p<pointsArray.length; p++) {
var pt = new google.maps.LatLng(pointsArray[p][0], pointsArray[p][1]);
points.push(pt);
bounds.extend(pt);
}
I got the following RequestAnimationframe function from http://my.opera.com/emoller/blog/2011/12/20/requestanimationframe-for-smart-er-animating
I am trying to use it. But not sure how to call it and use it. Can someone give me a simple example. I am new to this html5 animation thing so you can understand..
I will really appreciate any help! The function is below..
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+
'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}())
Just paste that code into your JS or in its own file and put this inside of your rendering function at the very bottom.
requestAnimationFrame(yourrenderingfunction);
Live Demo
// requestAnimationFrame shim
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelRequestAnimationFrame = window[vendors[x]+
'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}())
// Sprite unimportant, just for example purpose
function Sprite(){
this.x = 0;
this.y = 50;
}
Sprite.prototype.draw = function(){
ctx.fillStyle = "rgb(255,0,0)";
ctx.fillRect(this.x, this.y, 10, 10);
}
// setup
var canvas = document.getElementsByTagName("canvas")[0],
ctx = canvas.getContext("2d");
canvas.width = 200;
canvas.height = 200;
//init the sprite
var sprite = new Sprite();
// draw the sprite and update it using request animation frame.
function update(){
ctx.clearRect(0,0,200,200);
sprite.x+=0.5;
if(sprite.x>200){
sprite.x = 0;
}
sprite.draw();
// makes it update everytime
requestAnimationFrame(update);
}
// initially calls the update function to get it started
update();