sorry for what is probably a really dumb question but I am trying to learn how to use KineticJS and am trying to modify a tutorial (http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/) to use a static image instead of a shape. For what it's worth, I'm trying to animate a PNG of Glenn Beck's head to make it spin (neither here nor there really).
I've muddled through a bunch of errors so far but I keep getting stuck with "Uncaught TypeError: Object# has no method 'onFrame'"
I've read multiple questions about objects/methods here on SO and other sites and while I think I understand what the problem is, I'm not sure how to fix it:
object Object has no method
JavaScript object has no method
contains is object has no method?
From what I understand, the "no method" errors mean there is no function available to be called..? Surely "onFrame" exists inside Kinetic, though..? I tried looking through the Kinetic docs to see if they changed the name between 3.8.X (the tutorial) and 4.X (the library I am using) but it doesn't appear as though they did.
Here is my code:
<head>
<script src="http://test.XXXXX.com/js/kinetic-v4.3.2.js"></script>
<script>
function animate(animatedLayer, beck, frame){
var canvas = animatedLayer.getCanvas();
var context = animatedLayer.getContext();
// update
var angularFriction = 0.2;
var angularVelocityChange = beck.angularVelocity * frame.timeDiff * (1 - angularFriction) / 1000;
beck.angularVelocity -= angularVelocityChange;
if (beck.controlled) {
beck.angularVelocity = (beck.rotation - beck.lastRotation) * 1000 / frame.timeDiff;
beck.lastRotation = beck.rotation;
}
else {
beck.rotate(frame.timeDiff * beck.angularVelocity / 1000);
beck.lastRotation = beck.rotation;
}
// draw
animatedLayer.draw();
}
window.onload = function(){
console.log('stage =', stage); // DEBUG
var stage = new Kinetic.Stage({ container: "container", width: 240, height: 320 });
console.log('stage =', stage); // DEBUG
var backgroundLayer = new Kinetic.Layer();
var animatedLayer = new Kinetic.Layer();
var beck = new Image();
beck.onload = function() {
var beck = new Kinetic.Image({
x: 240,
y: stage.getHeight() / 2 - 59,
image: beckHead,
width: 150,
height: 230
});
beckHead.src = "http://test.XXXXX.com/i/beckhead.png";
animatedLayer.add(beck);
};
stage.on("mousedown", function(evt){
this.angularVelocity = 0;
this.controlled = true;
});
// add listeners to container
stage.on("mouseup", function(){
beck.controlled = false;
}, false);
stage.on("mouseout", function(){
beck.controlled = false;
}, false);
stage.on("mousemove", function(){
if (beck.controlled) {
var mousePos = stage.getMousePosition();
var x = (stage.width / 2) - mousePos.x;
var y = (stage.height / 2) - mousePos.y;
beck.rotation = 0.5 * Math.PI + Math.atan(y / x);
if (mousePos.x <= stage.width / 2) {
beck.rotation += Math.PI;
}
}
}, false);
stage.add(backgroundLayer);
stage.add(animatedLayer);
// draw background
var context = backgroundLayer.getContext();
context.save();
context.beginPath();
context.moveTo(stage.width / 2, stage.height / 2);
context.lineTo(stage.width / 2, stage.height);
context.strokeStyle = "#555";
context.lineWidth = 4;
context.stroke();
context.restore();
stage.onFrame(function(frame){
console.log("onFrame fired")
animate(animatedLayer, beck, frame);
});
stage.start();
};
</script>
</head>
<body onmousedown="return false;">
<div id="container"><canvas id="container"></canvas>
</div>
</body>
This is outdated example which uses version 3.8.4
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v3.8.4.js">
, and the current version is > 4.3
<script src="http://www.html5canvastutorials.com/libraries/kinetic-v4.3.0-beta2.js"></script>
The current version does not have methods like Stage#onFrame and Stage#start.
You can use this example, http://www.html5canvastutorials.com/kineticjs/html5-canvas-kineticjs-rotation-animation-tutorial/ as your base, then add stage.on("mouseup/down/move/out") to catch mouse movement and affect animation.
The perfect answer would be conversion of that old example to the new version one, which I may try in the future.
Related
I'm working on a small project in Javascript, using Pixi.js as the rendering engine. However, I've only found a few methods of scaling the canvas to full window that seem to work best for the current version. It does have a caveat, however, in that it produces letterboxes on the sides based on the orientation.
Is it possible to avoid the letterboxing at all with Pixi?
This is the code that I have so far, as it relates to the scaling:
var application = null;
var GAME_WIDTH = 1060;
var GAME_HEIGHT = 840;
var ratio = 0;
var stage = null;
application = new PIXI.Application(
{
width: GAME_WIDTH,
height: GAME_HEIGHT,
backgroundColor: 0x00b4f7,
view: document.getElementById("gwin")
});
stage = new PIXI.Container(true);
window.addEventListener("resize", rescaleClient);
function rescaleClient()
{
ratio = Math.min(window.innerWidth / GAME_WIDTH, window.innerHeight /
GAME_HEIGHT);
stage.scale.x = stage.scale.y = ratio;
application.renderer.resize(Math.ceil(GAME_WIDTH * ratio), Math.ceil(GAME_HEIGHT * ratio));
}
My goal with this is to achieve a similar full screen/window effect to Agar.io/Slither.io, however I have not discovered a satisfactory method that allows it easily. There do seem to be examples that use Pixi to achieve this, but the code is more then often closed source, and they seem to use external tools such as Ionic and Phonegap.
I finally found the answer. I was close to the right track, but a few more things needed to be applied.
application.renderer.view.style.position = "absolute";
application.renderer.view.style.display = "block";
application.renderer.autoResize = true;
application.renderer.resize(window.innerWidth, window.innerHeight);
This sets some additional things internally, while a minor modification to the resize script...
ratio = Math.min(window.innerWidth / GAME_WIDTH, window.innerHeight / GAME_HEIGHT);
stage.scale.x = stage.scale.y = ratio;
renderer.resize(window.innerWidth, window.innerHeight);
configures things correctly, so that the related Renderer window now fills the screen without squashing the content.
This was not easy to discover. So many tutorials just leave it at the first half, and assume that is what people wish to do.
var application;
//var GAME_WIDTH = window.screen.width-20;
var GAME_WIDTH = window.innerWidth;
//var GAME_WIDTH = document.body.clientWidth;
var GAME_HEIGHT = window.innerHeight;
var ratiox = 0;
var ratioy = 0;
var container;
application = new PIXI.Application(
{
width: GAME_WIDTH,
height: GAME_HEIGHT,
backgroundColor: 0x00b4f7,
view: document.getElementById("gwin")
});
//document.body.appendChild(application.view);
container = new PIXI.Container(true);
application.stage.addChild(container);
window.addEventListener("resize", rescaleClient);
function rescaleClient()
{
//ratiox = Math.min(window.innerWidth / GAME_WIDTH, window.innerHeight / GAME_HEIGHT);
application.stage.scale.x = ratiox = window.innerWidth / GAME_WIDTH
application.stage.scale.y = ratioy = window.innerHeight / GAME_HEIGHT;
application.renderer.resize(Math.ceil(GAME_WIDTH * ratiox), Math.ceil(GAME_HEIGHT * ratioy));
}
#viewport{
width:device-width
}
body {
padding :0px;
margin:0px
}
<script src="https://pixijs.download/v4.6.2/pixi.min.js"></script>
<canvas id="gwin"></canvas>
I am working with Processing.js (version 1.4.8).
I have 5 white points, which coordinates I chose specifically. The black dot marks the center of the sketch! I want to be able to translate and scale my sketch. ALSO, I want it to occupy the whole window.
var mapWidth, mapHeight, canvas, pjs, centerX, centerY;
var points = [[100, 100], [300, 100], [100, 300], [300, 300], [200, 200]];
var setSize = function() {
mapWidth = $(window).outerWidth();
mapHeight = $(window).outerHeight();
if (pjs) {
pjs.size(mapWidth, mapHeight);
}
};
var clear = function() {
pjs.background(200);
};
var drawPoint = function(coordinates) {
var radius = 30;
pjs.ellipse(coordinates[0], coordinates[1], radius, radius);
};
var drawPoints = function() {
pjs.fill(255);
points.map(function(point) {
drawPoint(point);
});
};
var calculateCenter = function() {
centerX = Math.floor(mapWidth / 2);
centerY = Math.floor(mapHeight / 2);
};
var drawCenter = function() {
calculateCenter();
var radius = 10;
pjs.fill(0);
pjs.ellipse(centerX, centerY, radius, radius);
console.log("center", centerX, centerY);
};
var move = function() {
pjs.translate(200, 300);
redraw();
};
var zoomIn = function() {
pjs.scale(2, 2);
redraw();
};
var draw = function() {
clear();
drawPoints();
drawCenter();
};
var redraw = function() {
clear();
draw();
};
var addEvent = function(object, type, callback) {
if (object == null || typeof object == "undefined") return;
if (object.addEventListener) {
object.addEventListener(type, callback, false);
} else if (object.attachEvent) {
object.attachEvent("on" + type, callback);
} else {
object["on" + type] = callback;
}
};
$(function() {
canvas = document.getElementById("map");
setSize();
var pjsRun = function(processingjs) {
pjs = processingjs;
pjs.setup = function() {
pjs.size(mapWidth, mapHeight);
draw();
};
};
var p = new Processing(canvas, pjsRun);
addEvent(window, "resize", function(event) {
setSize();
redraw();
});
});
Until here, everything is fine, as you can see in this CodePen.
I want to be able to resize the window AND keep the transformations (translations, scales, ...) that I had already performed.
Please, open the CodePen and try to reproduce this weird behaviour:
1) Perform one (or two) transformation(s) using the top-right buttons
The map is translated by 200 to the right and 300 downwards.
Everything OK by now...
But the problem arises now.
2) Resize the window
The five points are again where they were before the "translate" operation.
So... Again... Is there a way to resize without losing all the transformations that had been performed?
Thanks
Like you've discovered, it appears as though calling the size() function resets the transformation matrix. The short answer to your question is that you need to keep track of the transformations, and then apply them whenever you draw something.
The longer answer to your question is that you're using Processing.js a little bit differently than people typically use it. You've left out the draw() function (note that your draw() function is not the draw() function that's automatically called 60 times per second) and are trying to code event handlers yourself. This disconnect is why you're having issues.
If I were you, I'd start with a more basic sketch that starts out using Processing's built-in draw() function. Write code that draws the scene every frame. Make sure you set the translation and scale every frame. Here's an example:
var draw = function() {
scale(scaleX, scaleY);
translate(translateX, translateY);
background(200);
fill(255);
points.map(function(point) {
ellipse(coordinates[0], coordinates[1], 30, 30);
});
fill(0);
ellipse(width/2, height/2, 10, 10);
};
Then setup event listeners that change the values of scaleX and scaleY as well as translateX and translateY. Let Processing handle the rest.
I have come across a very odd behavior that is causing issues in Chrome (only tested browser). I have a canvas that is being used to render an audio frequency spectrum visual. Whenever I define the CanvasRenderingContext2D variable
var canvasCtx;
canvasCtx = canvas.getContext("2d");
or define the ScriptProcessorNode variable
var scriptNode;
scriptNode = audioCtx.createScriptProcessor(2048, 1, 1);
and the variable definition is inside any closure or function, the visualization rendering will freeze if you switch tabs, change windows, or even change the window width size.
However, I found out that if the variable definition for those two types are defined at the global scope outside all functions, the canvas will continue to render even if the page is inactive.
Here is a JSFiddle demonstrating the bug:
http://jsfiddle.net/K8J26/541/
(Audio may not play in JSFiddle due to cross-origin safety, so a local file may be needed)
I am wondering, why is this happening?
Does the browser hold onto the state of variables and objects that are tied to the global scope even if the page is inactive? Or is it something completely unrelated to the location of variable definition, and instead something I just completely overlooked?
Thanks!
Your problem seems to be that you are calculating the new size and creating the gradient while the browser has not updated the size of your elements yet.
Pass the updateScale in a requestAnimationFrame call so the browser has the time to render the elements :
(function initVisualizer() {
var AudioContext = window.AudioContext || window.webkitAudioContext;
if (!AudioContext) {
return;
}
var PERCENT_HIGH_FREQ_TRIM = 0.3;
var PERCENT_HEIGHT_OF_WITDH = 0.25;
var SMOTHING_CONSTANT = 0.6;
var audioCtx = new AudioContext();
var canvas = document.getElementById("visualizer");
canvas.style.width = "100%";
// Defining canvasCtx here causes frozen canvas rendering when tab is inactive
var canvasCtx = canvas.getContext("2d");
// Defining scriptNode here causes frozen canvas rendering when tab is inactive
var scriptNode = audioCtx.createScriptProcessor(2048, 1, 1);
scriptNode.connect(audioCtx.destination);
var analyser = audioCtx.createAnalyser();
analyser.smoothingTimeConstant = SMOTHING_CONSTANT;
analyser.connect(scriptNode);
var widthMax;
var heightMax;
var dataCount;
var barWidth;
var barOffset;
var gradient;
scriptNode.onaudioprocess = function() {
var frequencyData = new Uint8Array(dataCount);
analyser.getByteFrequencyData(frequencyData);
canvasCtx.clearRect(0, 0, widthMax, heightMax);
canvasCtx.fillStyle = gradient;
var modifier = heightMax / 255;
for (var i = 0; i < dataCount; i++) {
var value = ~~(frequencyData[i] * modifier);
canvasCtx.fillRect(i * (barWidth + barOffset), heightMax - value, barWidth, heightMax);
}
};
window.addEventListener("resize", function() {
if (canvas.parentElement.offsetWidth !== widthMax) {
requestAnimationFrame(updateScale);
}
});
function updateScale() {
widthMax = canvas.offsetWidth;
heightMax = ~~(widthMax * PERCENT_HEIGHT_OF_WITDH);
var size = nearestPow2(~~(widthMax / 4));
dataCount = ~~((size / 2) * (1 - PERCENT_HIGH_FREQ_TRIM));
var rectWidth = widthMax / dataCount;
barOffset = rectWidth * 0.25;
barWidth = rectWidth - barOffset;
analyser.fftSize = size;
canvas.setAttribute("width", widthMax);
canvas.setAttribute("height", heightMax);
gradient = canvasCtx.createLinearGradient(0, 0, 0, heightMax);
gradient.addColorStop(1, "#ff6600");
gradient.addColorStop(0, "#ffff00");
}
function playAudio(audio) {
var sourceNode = audioCtx.createMediaElementSource(audio);
sourceNode.connect(analyser);
sourceNode.connect(audioCtx.destination);
console.log(audio.play());
setTimeout(function() {
audio.pause();
}, 30000);
}
function nearestPow2(size) {
return Math.pow(2, Math.round(Math.log(size) / Math.log(2)));
}
updateScale();
playAudio(document.getElementById('audio'));
})();
body {
background: #222;
}
<audio id="audio" controls crossorigin="anonymous">
<source src="https://upload.wikimedia.org/wikipedia/en/d/dc/Strawberry_Fields_Forever_%28Beatles_song_-_sample%29.ogg" />
<source src="https://upload.wikimedia.org/wikipedia/en/d/dc/Strawberry_Fields_Forever_%28Beatles_song_-_sample%29.ogg" />
</audio>
<canvas id="visualizer"></canvas>
However I don't know why it was working when you set the variable in global context...
Ps : your fiddle wasn't working on FF and only worked on chrome because of a bug, you need to set the crossorigin attribute before the resource are loaded, otherwise it has no effect.
Is it possible to create two layers (with one being translucent) in OpenLayers and move them independently? If so, how?
I want to let the user choose which layer to move or if that's not possible, move one layer via my own JavaScript code while the other is controlled by the user.
Both will be prerendered pixmap layers, if that is important.
This is the solution I came up with. It isn't pretty but it works for my purposes.
Better alternatives are very welcome ...
/**
* #requires OpenLayers/Layer/TMS.js
*/
MyLayer = OpenLayers.Class(OpenLayers.Layer.TMS, {
latShift: 0.0,
latShiftPx: 0,
setMap: function(map) {
OpenLayers.Layer.TMS.prototype.setMap.apply(this, arguments);
map.events.register("moveend", this, this.mapMoveEvent)
},
// This is the function you will want to modify for your needs
mapMoveEvent: function(event) {
var resolution = this.map.getResolution();
var center = this.map.getCenter();
// This is some calculation I use, replace it whatever you like:
var h = center.clone().transform(projmerc, proj4326);
var elliptical = EllipticalMercator.fromLonLat(h.lon, h.lat);
var myCenter = new OpenLayers.LonLat(elliptical.x, elliptical.y);
this.latShift = myCenter.lat - center.lat;
this.latShiftPx = Math.round(this.latShift/resolution);
this.div.style.top = this.latShiftPx + "px";
},
moveTo: function(bounds, zoomChanged, dragging) {
bounds = bounds.add(0, this.latShift);
OpenLayers.Layer.TMS.prototype.moveTo.apply(this, [bounds, zoomChanged, dragging]);
},
// mostly copied and pasted from Grid.js ...
moveGriddedTiles: function() {
var buffer = this.buffer + 1;
while(true) {
var tlTile = this.grid[0][0];
var tlViewPort = {
x: tlTile.position.x +
this.map.layerContainerOriginPx.x,
y: tlTile.position.y +
this.map.layerContainerOriginPx.y + this.latShiftPx // ... except this line
};
var ratio = this.getServerResolution() / this.map.getResolution();
var tileSize = {
w: Math.round(this.tileSize.w * ratio),
h: Math.round(this.tileSize.h * ratio)
};
if (tlViewPort.x > -tileSize.w * (buffer - 1)) {
this.shiftColumn(true, tileSize);
} else if (tlViewPort.x < -tileSize.w * buffer) {
this.shiftColumn(false, tileSize);
} else if (tlViewPort.y > -tileSize.h * (buffer - 1)) {
this.shiftRow(true, tileSize);
} else if (tlViewPort.y < -tileSize.h * buffer) {
this.shiftRow(false, tileSize);
} else {
break;
}
}
},
CLASS_NAME: "MyLayer"
});
Note that this only works with OpenLayers 2.13 or newer
I start on KineticJS (and on Canvas) and i'm creating a small game for learn...
Right now, I have just 2 layers :
First with a map composed by Kinetic.Image
Second with the last time who game as draw.
I want refresh display X time per second but after 20 or 30 times the game are really slow.. And it's the same when I flood event click ( who launch the draw function too)...
Moreover, i can see in the second layer : the old text are never clean, the new are added on top... :/
var stage;
var layers = {};
var CANEVAS_WIDTH = 800;
var CANEVAS_HEIGHT = 600;
var MAP_WIDTH = 10;
var MAP_HEIGHT = 10;
var MAPPING_WIDTH = 150;
var MAPPING_HEIGHT = 88;
var LEFT_X = 0;
var LEFT_Y = MAP_WIDTH*MAPPING_HEIGHT/2;
var TOP_X = MAP_WIDTH/2*MAPPING_WIDTH;
var TOP_Y = 0;
var VIEW_X = 0;
var VIEW_Y = 0;
var CURSOR_X = 6;
var CURSOR_Y = 0;
var images = {};
function loadImages(sources, callback)
{
var loadedImages = 0;
var numImages = 0;
// get num of sources
for (var src in sources)
numImages++;
for (var src in sources)
{
images[src] = new Image();
images[src].onload = function(){
if (++loadedImages >= numImages)
callback();
};
images[src].src = sources[src];
}
}
function getMouseInfo(mousePos)
{
var info = {screen_x : mousePos.x,
screen_y : mousePos.y,
mouse_x : mousePos.x+VIEW_X,
mouse_y : mousePos.y+VIEW_Y-LEFT_Y,
onMap : 0,
map_x : -1,
map_y : -1};
map_x = -(info.mouse_y - ((LEFT_Y * info.mouse_x) / TOP_X)) / MAPPING_HEIGHT;
map_y = -(-info.mouse_y - ((LEFT_Y * info.mouse_x) / TOP_X)) / MAPPING_HEIGHT;
if(map_x >= 0 && map_x < MAP_WIDTH && map_y >= 0 && map_y < MAP_HEIGHT)
{
info.map_y = parseInt(map_y);
info.map_x = parseInt(map_x);
info.onMap = 1;
}
return info;
}
function draw()
{
drawMap();
drawFPS();
stage.add(layers.mapLayer);
stage.add(layers.fpsLayer);
}
function drawFPS()
{
layers.fpsLayer.clear();
var fps = new Kinetic.Shape(function(){
var date = new Date();
var time = date.getTime();
var context = this.getContext();
context.beginPath();
context.font = "12pt Calibri";
context.fillStyle = "red";
context.fillText("FPS : "+time, 10, 20);
});
layers.fpsLayer.add(fps);
}
function drawMap()
{
var x=0,y=0;
layers.mapLayer.clear();
var s = new Kinetic.Shape(function(){
var context = this.getContext();
context.beginPath();
context.rect(0, 0, CANEVAS_WIDTH, CANEVAS_HEIGHT);
context.fillStyle = "#000";
context.fill();
context.closePath();
});
layers.mapLayer.add(s);
for(x=0; x<MAP_WIDTH; x++)
for(y=0;y<MAP_HEIGHT; y++)
{
var img = new Kinetic.Image({
image: ((x==CURSOR_X && y==CURSOR_Y)?images.testMapCursor:images.testMap)
});
img.x = x*MAPPING_WIDTH/2 + y*MAPPING_WIDTH/2 - VIEW_X;
img.y = (MAP_WIDTH-1)*MAPPING_HEIGHT/2 - x*MAPPING_HEIGHT/2 + y*MAPPING_HEIGHT/2 - VIEW_Y;
layers.mapLayer.add(img);
}
}
function changeCursorPosition(cursor_x, cursor_y)
{
CURSOR_X = cursor_x;
CURSOR_Y = cursor_y;
draw();
}
function initStage()
{
layers.mapLayer = new Kinetic.Layer();
layers.fpsLayer = new Kinetic.Layer();
draw();
}
/*
* INIT
*/
window.onload = function(){
stage = new Kinetic.Stage("container", <?=CANEVAS_WIDTH;?>, <?=CANEVAS_HEIGHT;?>);
stage.on("mousemove", function(){
var mouseInfo = getMouseInfo(stage.getMousePosition());
if(mouseInfo.onMap)
document.body.style.cursor = "pointer";
else
document.body.style.cursor = "default";
});
stage.on("mousedown", function(){
var mouseInfo = getMouseInfo(stage.getMousePosition());
if(mouseInfo.onMap)
changeCursorPosition(mouseInfo.map_x, mouseInfo.map_y);
});
var sources = {
testMap : "testMap.png",
testMapCursor : "testMapCursor.png"
};
loadImages(sources, initStage);
};
Sorry, my english are realy bad.
Thank all.
I know someone who is trying out KineticJS. I haven't used it myself, so I apologize that I cannot provide more specific help.
Unfortunately, it is very difficult to get good performance with canvas, and it depends greatly on the browser. Last I checked, Opera 12 and IE 9 performed significantly faster than other browsers, since their 2D rendering is 3D accelerated (using OpenGL and Direct3D, respectively)
I am not sure if this applies to KineticJS, but one technique you can use to improve performance with canvas is to use multiple canvas elements, and transform their positions rather than blitting on a single surface.
I've been pretty happy with the results I've gotten using Jeash, which is wired into NME's command-line tools. The development is similar to working with Flash, but it will create an HTML5 Canvas application using your code. The same application will also be able to publish to Windows, Mac, Linux, iOS, Android, webOS and Flash, as either native C++ and OpenGL, or as SWF bytecode. This gives you a lot of options for providing the best experience for each user.
http://www.haxenme.org