Ball movement path in canvas, and other ideas you may have? - html

I've created this animation for my project that had to use any form of physics.
I am a total beginner, too :) Anyway, this is my project now :
Bouncing Balls
You can setup gravity and force, then click play, and just drag and drop to shoot the balls. You can change the values and hit update too see an effect.
My question is, how can I create an effect that when I press ratio button (for example) I can see the path that ball makes? Is it complicated? As I was saying I am a beginner, so no complex code for me :)
Also, doyou have any ideas to make the project better? Any additional "physics" effects? Or maybe you know a website that shows tutorials for simile (please) effects made in HTML5/js so I can add additional effects to my project.

One possibility (as you're clearing the canvas each frame) would be to draw ball paths onto a secondary canvas, which would not be cleared each frame. Then, when you come to clear the first frame, render the second frame after clearing, and before rendering the balls.
The second canvas would of course have to be the same dimensions as the first, so that all of the ball points line up correctly. The second canvas should also have a z-index lower than the first, so that it is only shown when you specifically render it to the first canvas (i.e. when the radio button is checked).
To decrease any lag while the radio is not checked, you could skip drawing the ball paths to the second canvas, although I don't think you would see any great increase in performance.
On each frame update, you would mark the position of each ball with a pixel, or line (from the previous position to the current) on the second canvas.
Looking at your code, you seem pretty competent, so I've skipped writing an example as I think this would be good experience for you :)
Modified 'script.js' source demonstrating solution
window.onload = function(){
$("#canvas").hide();
var howManyPaths = 0;
var showPath=false;
// SLIDERS
var gravitySlider = document.getElementById('gravitySlider');
var gravityVal = document.getElementById('gravityValue');
gravitySlider.onchange = function(){
gravityVal.value = gravitySlider.value;
}
gravityVal.onkeyup = function(){
gravitySlider.value = gravityVal.value;
}
var forceSlider = document.getElementById('forceSlider');
var forceValue = document.getElementById('forceValue');
forceSlider.onchange = function(){
forceValue.value = forceSlider.value;
}
forceValue.onkeyup = function(){
forceSlider.value = forceValue.value;
}
// GLOBAL VARIABLES
var test = false;
var gravityCount = $("#gravity").val();
var forceCount = $("#rectangles").val();
// CSS :
var playCSS = document.getElementById("play");
var restartCSS = document.getElementById("restart");
var clickableCSS = document.getElementById("setup");
var clickableBG = document.getElementById("img");
//restartCSS.style.visibility="hidden";
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var canvas2 = document.getElementById("canvas2");
var ctx2 = canvas2.getContext("2d");
//var ctx;
var gravity = 9.86;
var forceFactor = 0.5;
var mouseDown = false;
var balls = new Array();
var mousePos = new Array();
// EVENT HANDLER
function onMouseDown(evt){
mouseDown = true;
mousePos['downX'] = evt.pageX;
mousePos['downY'] = evt.pageY;
}
function onMouseUp(evt){
mouseDown = false;
setup.style.visibility="visible";
if(test == true && !( mousePos['downX'] < 200 && mousePos['downY'] < 150) ){
restartCSS.style.visibility="visible";
forceFactor = forceCount;
balls.push(new ball(mousePos["downX"],
mousePos["downY"],
(evt.pageX - mousePos["downX"]) * forceFactor,
(evt.pageY - mousePos["downY"]) * forceFactor,
10 + (Math.random() * 10),
0.8,
randomColor()
));
}
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
}
function onMouseMove(evt){
mousePos['currentX'] = evt.pageX;
mousePos['currentY'] = evt.pageY;
}
function resizeWindow(evt){
//canvas.height = 960;
//canvas.width = 720;
canvas.height = $(window).height()-6;
canvas.width = $(window).width();
canvas2.height = $(window).height()-6;
canvas2.width = $(window).width();
}
$(document).mousedown(onMouseDown);
$(document).mouseup(onMouseUp);
$(document).mousemove(onMouseMove);
$(window).bind("resize", resizeWindow);
// GRAPHICS CODE
function circle(x, y, r, col){
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI*2, true);
ctx.closePath;
// fill
ctx.fillStyle = col;
ctx.fill();
// stroke
ctx.lineWidth = r * 0.1;
ctx.strokeStyle = "#000000";
ctx.stroke();
}
function circlePath(x, y)
{
ctx2.clearRect(0, 0, canvas2.width, canvas2.height);
ctx2.fillStyle = '#3f4043';
ctx2.fillRect(x, y, 5, 5);
ctx2.strokeStyle = "black";
ctx2.strokeRect(x, y, 5, 5);
}
function randomColor(){
var letter = "0123456789ABCDEF".split("");
var color = "#";
for(var i=0; i<6; i++){
color += letter[Math.round(Math.random()*15)];
}
return color;
}
function arrow(fromX, fromY, toX, toY, color){
// path
ctx.beginPath();
var headLen = 10;
var angle = Math.atan2(toY - fromY, toX - fromX);
ctx.moveTo(fromX, fromY);
ctx.lineTo(toX, toY);
ctx.lineTo(toX - headLen * Math.cos(angle - Math.PI/6), toY - headLen * Math.sin(angle - Math.PI/6));
ctx.moveTo(toX, toY);
ctx.lineTo(toX - headLen * Math.cos(angle + Math.PI/6), toY - headLen * Math.sin(angle + Math.PI/6));
// style
ctx.lineWith = 1;
ctx.strokeStyle = color;
ctx.lineCap = "butt";
ctx.stroke();
}
function drawBall(){
// Gravity
gravity = gravityCount;
this.speedY += gravity * 0.5; // v = a * t
this.x += this.speedX * 0.05; // s = v * t
this.y += this.speedY * 0.05;
// prawa ściana
if(this.x + this.r > canvas.width){
this.x = canvas.width - this.r;
this.speedX *= -1 * this.bounce;
}
// lewa ściana
if(this.x - this.r < 0){
this.x = this.r;
this.speedX *= -1 * this.bounce;
}
// dolna ściana
if(this.y + this.r > canvas.height){
this.y = canvas.height - this.r;
this.speedY *= -1 * this.bounce;
}
// górna ściana
if(this.y - this.r < 0){
this.y = this.r;
this.speedY *= -1 * this.bounce;
}
// zwalnianie na ziemi
if (this.speedX > 0.25){
this.speedX -= 0.25;
if (this.speedY > 0.25)
this.speedY -= 0.25;
}
if (this.speedX < -0.25){
this.speedX += 0.25;
//if (this.speedY < -0.25)
// this.speedY += 0.25;
}
circle(this.x, this.y, this.r, this.col);;
}
// OBJECTS
function ball(positionX, positionY, sX, sY, radius, b, color){
this.x = positionX;
this.y = positionY;
this.speedX = sX;
this.speedY = sY;
this.r = radius;
this.bounce = b;
this.col = color;
this.draw = drawBall;
}
//GAME LOOP
function gameLoop(){
ctx.clearRect(0, 0, canvas.width, canvas.height);
//grab the context from your destination canvas
//if path drawing is enabled, first draw the path canvas to the display canvas
if (showPath) ctx.drawImage(canvas2,0,0);
if(mouseDown == true){
// ctx.clearRect(0, 0, canvas.width, canvas.height); /* !important !!!!!!!!!!!!!!! */
arrow(mousePos['downX'], mousePos['downY'], mousePos['currentX'], mousePos['currentY'], "red");
}
for(var i=0; i<balls.length; i++){
balls[i].draw();
if (i==balls.length-1) {
//draw path
ctx2.fillStyle = '#3f4043';
ctx2.fillRect(balls[i].x, balls[i].y, 5, 5);
ctx2.strokeStyle = "black";
ctx2.strokeRect(balls[i].x, balls[i].y, 5, 5);
}
}
ctx.fillStyle = "#000000";
ctx.font = "15px Arial";
ctx.fillText("Balls: " + balls.length + " " + gravityCount + " " + forceCount + " " + howManyPaths, 10, canvas.height -10);
}
// START THE GAME
function init(){
//$("#setup").hide();
$("#canvas").show();
$("#canvas2").hide();
ctx = $('canvas')[0].getContext("2d");
canvas.height = $(window).height()-6;
canvas.width = $(window).width();
//canvas.width = 960;
//canvas.height = 720;
canvas2.height = $(window).height()-6;
canvas2.width = $(window).width();
return setInterval(gameLoop, 10);
}
$("#play").click(function() {
test = true;
playCSS.style.visibility="hidden";
gravityCount = $("#gravitySlider").val();
forceCount = $("#forceSlider").val();
init();
});
$("#restart").click(function() {
window.location.href="index.html";
});
$("#refresh").click(function() {
gravityCount = $("#gravitySlider").val();
forceCount = $("#forceSlider").val();
});
$("#showPath").click(function() {
showPath=true;
});
$("#hidePath").click(function() {
showPath=false;
});
}

Related

Html5 canvas scrolling vertically and horizontally

<!DOCTYPE html>
<html>
<head>
<style type="text/css">
#canvasOne
{
border: 1px solid black;
}
</style>
<script src="http://code.jquery.com/jquery-1.10.2.js" type="text/javascript"></script>
</head>
<body>
<div align="center">
<canvas id="canvasOne">
</canvas>
</div>
<script type="text/javascript">
var myCanvas = document.getElementById("canvasOne");
var myContext = myCanvas.getContext("2d");
var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
init();
var numShapes;
var shapes;
var dragIndex;
var dragging;
var mouseX;
var mouseY;
var dragHoldX;
var dragHoldY;
var timer;
var targetX;
var targetY;
var easeAmount;
var bgColor;
var nodes;
var colorArr;
function init()
{
myCanvas.width = $(window).width() - 200;
myCanvas.height = $(window).height() - 200;
shapes = [];
nodes = ["0;Person;24828760;Alok Kumar;Gorakhpur;#F44336;28",
"0;Suspect;04/Dec/2016;4;Suman_Biswas;#3F51B5;20","1;Rule;4;Apparent Means;3 Parameter;#EEFF41;20",
"0;Policy;36QA649749;In-Force;Quarterly;#FF9800;20","3;Product;Pension;Saral Pension;SRPEN;#795548;20","3;Payment;Cheque;Realized;Lucknow;#0091EA;20",
"0;Policy;162348873;Lapsed;Quarterly;#FF9800;20","6;Product;Pension;Life-Long Pension;LLPP;#795548;20","6;Payment;Cheque;Realized;Gorakhpur;#0091EA;20",
"0;Policy;1EQF178639;Lapsed;Monthly;#FF9800;20","9;Product;Life;Shield;SHIELDA;#795548;20","9;Payment;Demand Draft;Realized;Lucknow;#0091EA;20"];
numShapes = nodes.length;
makeShapes();
drawScreen();
myCanvas.addEventListener("mousedown", mouseDownListener, false);
}
//drawing
function makeShapes()
{
var tempX;
var tempY;
for(var i = 0; i < numShapes; i++)
{
var centerX = myCanvas.width/2;
var centerY = myCanvas.height/2;
var nodeColor = nodes[i].split(";")[5];
var nodeRadius = nodes[i].split(";")[6];
var nodeConnect = nodes[i].split(";")[0];
if(i == 0)//center of circle
{
tempX = centerX
tempY = centerY;
}
else
{
//tempX = Math.random() * (myCanvas.width - tempRadius);
//tempY = Math.random() * (myCanvas.height - tempRadius);
//var x = x0 + r * Math.cos(2 * Math.PI * i / items);
//var y = y0 + r * Math.sin(2 * Math.PI * i / items);
//250 is the distance from center node to outside nodes it can be actual radius in degrees
tempX = shapes[nodeConnect].x + 300 * Math.cos(2 * Math.PI * i / numShapes);
tempY = shapes[nodeConnect].y + 300 * Math.sin(2 * Math.PI * i / numShapes);
}
tempShape = {x: tempX, y: tempY, rad: nodeRadius, color: nodeColor, text: nodes[i]};
shapes.push(tempShape);
}
}
//drawing both shape (line and circle) and screen
function drawScreen()
{
myContext.fillStyle = "#ffffff";
myContext.fillRect(0, 0, myCanvas.width, myCanvas.height);
drawShapes();
}
function drawShapes()
{
//line
for(var i = 1; i < numShapes; i++)
{
myContext.beginPath();
myContext.strokeStyle = "#B2B19D";
var nodeConnect = nodes[i].split(";")[0];
myContext.moveTo(shapes[nodeConnect].x, shapes[nodeConnect].y);
myContext.lineTo(shapes[i].x, shapes[i].y);
myContext.stroke();
}
//circle
for(var i = 0; i < numShapes; i++)
{
myContext.fillStyle = shapes[i].color;
myContext.beginPath();
myContext.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2*Math.PI, false);
myContext.closePath();
myContext.fill();
}
//text
for(var i = 0; i < numShapes; i++)
{
myContext.beginPath();
myContext.font = '10pt Arial';
myContext.fillStyle = 'black';
var textarr = shapes[i].text.split(";");
myContext.fillText(textarr[1], shapes[i].x + 30, shapes[i].y - 24);
/*myContext.fillText(textarr[2], shapes[i].x + 30, shapes[i].y + 1);
myContext.fillText(textarr[3], shapes[i].x + 30, shapes[i].y + 22);
myContext.fillText(textarr[4], shapes[i].x + 30, shapes[i].y + 44);*/
myContext.closePath();
myContext.fill();
}
}
//animation
function mouseDownListener(evt)
{
var highestIndex = -1;
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left) * (myCanvas.width/bRect.width);
mouseY = (evt.clientY - bRect.top) * (myCanvas.height/bRect.height);
for(var i = 0; i < numShapes; i++)
{
if(hitTest(shapes[i], mouseX, mouseY))
{
dragging = true;
if(i > highestIndex)
{
dragHoldX = mouseX - shapes[i].x;
dragHoldY = mouseY - shapes[i].y;
highestIndex = i;
dragIndex = i;
}
}
}
if(dragging)
{
window.addEventListener("mousemove", mouseMoveListener, false);
}
myCanvas.removeEventListener("mousedown", mouseDownListener, false);
window.addEventListener("mouseup", mouseUpListener, false);
if(evt.preventDefault)
{
evt.preventDefault;
}
return false;
}
function mouseMoveListener(evt)
{
var shapeRad = shapes[dragIndex].rad;
var minX = shapeRad;
var maxX = myCanvas.width - shapeRad;
var minY = shapeRad;
var maxY = myCanvas.height - shapeRad;
//get mouse position correctly
var bRect = myCanvas.getBoundingClientRect();
mouseX = (evt.clientX - bRect.left)*(myCanvas.width / bRect.width);
mouseY = (evt.clientY - bRect.top)*(myCanvas.height / bRect.height);
//clamp x and y position to prevent object from dragging outside canvas
posX = mouseX - dragHoldX;
posX = (posX < minX) ? minX : ((posX > maxX) ? maxX : posX);
posY = mouseY - dragHoldY;
posY = (posY < minY) ? minY : ((posY > maxY) ? maxY : posY);
shapes[dragIndex].x = posX;
shapes[dragIndex].y = posY;
drawScreen();
}
function mouseUpListener(evt)
{
myCanvas.addEventListener("mousedown", mouseDownListener, false);
window.removeEventListener("mouseup", mouseUpListener, false);
if(dragging)
{
dragging = false;
window.removeEventListener("mousemove", mouseMoveListener, false);
}
}
function hitTest(shape, mx, my)
{
var dx = mx - shape.x;
var dy = my - shape.y;
return(dx * dx + dy * dy < shape.rad * shape.rad);
}
</script>
</body>
</html>
The following canvas animation creates nodes and edges. However due
to space constraint, some of the nodes are not visible due to canvas
height and width. Even adding overflow css to canvas dosen't help as
i am not able to scroll.
<canvas> context doesn't have a built-in scroll method.
You then have multiple ways to circumvent this limitation.
The first one, is as in #markE's answer, to scale your context's matrix so that your drawings fit into the required space. You could also refactor your code so that all coordinates are relative to the canvas size.
This way, you won't need scrollbars and all your drawings will just be scaled appropriately, which is the desirable behavior in most common cases.
But if you really need to have some scrolling feature, here are some ways :
The easiest and most recommended one : let the browser handle it.
You will have to set the size of your canvas to the maximum of your drawings, and wrap it in an other element which will scroll. By setting the overflow:auto css property on the container, our scrollbars appear and we have our scrolling feature.
In following example, the canvas is 5000px wide and the container 200px.
var ctx = canvas.getContext('2d');
ctx.textAlign = 'center';
for (var w = 0; w < canvas.width; w += 100) {
for (var h = 0; h < canvas.height; h += 100) {
ctx.fillText(w + ',' + h, w, h);
}
}
#container {
width: 200px;
height: 200px;
overflow: auto;
border: 1px solid;
}
canvas{
display: block;
}
<div id="container">
<canvas id="canvas" height="5000" width="5000"></canvas>
</div>
Main advantages :
easily implemented.
users are used to these scrollbars.
Main caveats :
You're limited by canvas maximum sizes.
If your canvas is animated, you'll also draw for each frame parts of the canvas that aren't visible.
You have small control on scrollbars look and you'll still have to implement drag-to-scroll feature yourself for desktop browsers.
A second solution, is to implement this feature yourself, using canvas transform methods : particularly translate, transform and setTransform.
Here is an example :
var ctx = canvas.getContext('2d');
var app = {};
// the total area of our drawings, can be very large now
app.WIDTH = 5000;
app.HEIGHT = 5000;
app.draw = function() {
// reset everything (clears the canvas + transform + fillStyle + any other property of the context)
canvas.width = canvas.width;
// move our context by the inverse of our scrollbars' left and top property
ctx.setTransform(1, 0, 0, 1, -app.scrollbars.left, -app.scrollbars.top);
ctx.textAlign = 'center';
// draw only the visible area
var visibleLeft = app.scrollbars.left;
var visibleWidth = visibleLeft + canvas.width;
var visibleTop = app.scrollbars.top
var visibleHeight = visibleTop + canvas.height;
// you probably will have to make other calculations than these ones to get your drawings
// to draw only where required
for (var w = visibleLeft; w < visibleWidth + 50; w += 100) {
for (var h = visibleTop; h < visibleHeight + 50; h += 100) {
var x = Math.round((w) / 100) * 100;
var y = Math.round((h) / 100) * 100;
ctx.fillText(x + ',' + y, x, y);
}
}
// draw our scrollbars on top if needed
app.scrollbars.draw();
}
app.scrollbars = function() {
var scrollbars = {};
// initial position
scrollbars.left = 0;
scrollbars.top = 0;
// a single constructor for both horizontal and vertical
var ScrollBar = function(vertical) {
var that = {
vertical: vertical
};
that.left = vertical ? canvas.width - 10 : 0;
that.top = vertical ? 0 : canvas.height - 10;
that.height = vertical ? canvas.height - 10 : 5;
that.width = vertical ? 5 : canvas.width - 10;
that.fill = '#dedede';
that.cursor = {
radius: 5,
fill: '#bababa'
};
that.cursor.top = vertical ? that.cursor.radius : that.top + that.cursor.radius / 2;
that.cursor.left = vertical ? that.left + that.cursor.radius / 2 : that.cursor.radius;
that.draw = function() {
if (!that.visible) {
return;
}
// remember to reset the matrix
ctx.setTransform(1, 0, 0, 1, 0, 0);
// you can give it any shape you like, all canvas drawings operations are possible
ctx.fillStyle = that.fill;
ctx.fillRect(that.left, that.top, that.width, that.height);
ctx.beginPath();
ctx.arc(that.cursor.left, that.cursor.top, that.cursor.radius, 0, Math.PI * 2);
ctx.fillStyle = that.cursor.fill;
ctx.fill();
};
// check if we're hovered
that.isHover = function(x, y) {
if (x >= that.left - that.cursor.radius && x <= that.left + that.width + that.cursor.radius &&
y >= that.top - that.cursor.radius && y <= that.top + that.height + that.cursor.radius) {
// we are so record the position of the mouse and set ourself as the one hovered
scrollbars.mousePos = vertical ? y : x;
scrollbars.hovered = that;
that.visible = true;
return true;
}
// we were visible last call and no wheel event is happening
else if (that.visible && !scrollbars.willHide) {
that.visible = false;
// the app should be redrawn
return true;
}
}
return that;
};
scrollbars.horizontal = ScrollBar(0);
scrollbars.vertical = ScrollBar(1);
scrollbars.hovered = null;
scrollbars.dragged = null;
scrollbars.mousePos = null;
// check both of our scrollbars
scrollbars.isHover = function(x, y) {
return this.horizontal.isHover(x, y) || this.vertical.isHover(x, y);
};
// draw both of our scrollbars
scrollbars.draw = function() {
this.horizontal.draw();
this.vertical.draw();
};
// check if one of our scrollbars is visible
scrollbars.visible = function() {
return this.horizontal.visible || this.vertical.visible;
};
// hide it...
scrollbars.hide = function() {
// only if we're not using the mousewheel or dragging the cursor
if (this.willHide || this.dragged) {
return;
}
this.horizontal.visible = false;
this.vertical.visible = false;
};
// get the area's coord relative to our scrollbar
var toAreaCoord = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = (pos - sbBase) / (sbMax - sbBase);
return areaMax * ratio;
};
// get the scrollbar's coord relative to our total area
var toScrollCoords = function(pos, scrollBar) {
var sbBase = scrollBar.vertical ? scrollBar.top : scrollBar.left;
var sbMax = scrollBar.vertical ? scrollBar.height : scrollBar.width;
var areaMax = scrollBar.vertical ? app.HEIGHT - canvas.height : app.WIDTH - canvas.width;
var ratio = pos / areaMax;
return ((sbMax - sbBase) * ratio) + sbBase;
}
scrollbars.scroll = function() {
// check which one of the scrollbars is active
var vertical = this.hovered.vertical;
// until where our cursor can go
var maxCursorPos = this.hovered[vertical ? 'height' : 'width'];
var pos = vertical ? 'top' : 'left';
// check that we're not out of the bounds
this.hovered.cursor[pos] = this.mousePos < 0 ? 0 :
this.mousePos > maxCursorPos ? maxCursorPos : this.mousePos;
// seems ok so tell the app we scrolled
this[pos] = toAreaCoord(this.hovered.cursor[pos], this.hovered);
// redraw everything
app.draw();
}
// because we will hide it after a small time
scrollbars.willHide;
// called by the wheel event
scrollbars.scrollBy = function(deltaX, deltaY) {
// it's not coming from our scrollbars
this.hovered = null;
// we're moving horizontally
if (deltaX) {
var newLeft = this.left + deltaX;
// make sure we're in the bounds
this.left = newLeft > app.WIDTH - canvas.width ? app.WIDTH - canvas.width : newLeft < 0 ? 0 : newLeft;
// update the horizontal cursor
this.horizontal.cursor.left = toScrollCoords(this.left, this.horizontal);
// show our scrollbar
this.horizontal.visible = true;
}
if (deltaY) {
var newTop = this.top + deltaY;
this.top = newTop > app.HEIGHT - canvas.height ? app.HEIGHT - canvas.height : newTop < 0 ? 0 : newTop;
this.vertical.cursor.top = toScrollCoords(this.top, this.vertical);
this.vertical.visible = true;
}
// if we were called less than the required timeout
clearTimeout(this.willHide);
this.willHide = setTimeout(function() {
scrollbars.willHide = null;
scrollbars.hide();
app.draw();
}, 500);
// redraw everything
app.draw();
};
return scrollbars;
}();
var mousedown = function(e) {
// tell the browser we handle this
e.preventDefault();
// we're over one the scrollbars
if (app.scrollbars.hovered) {
// new promotion ! it becomes the dragged one
app.scrollbars.dragged = app.scrollbars.hovered;
app.scrollbars.scroll();
}
};
var mousemove = function(e) {
// check the coordinates of our canvas in the document
var rect = canvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
// we're dragging something
if (app.scrollbars.dragged) {
// update the mouse position
app.scrollbars.mousePos = app.scrollbars.dragged.vertical ? y : x;
app.scrollbars.scroll();
} else if (app.scrollbars.isHover(x, y)) {
// something has changed, redraw to show or hide the scrollbar
app.draw();
}
e.preventDefault();
};
var mouseup = function() {
// we dropped it
app.scrollbars.dragged = null;
};
var mouseout = function() {
// we're out
if (app.scrollbars.visible()) {
app.scrollbars.hide();
app.scrollbars.dragged = false;
app.draw();
}
};
var mouseWheel = function(e) {
e.preventDefault();
app.scrollbars.scrollBy(e.deltaX, e.deltaY);
};
canvas.addEventListener('mousemove', mousemove);
canvas.addEventListener('mousedown', mousedown);
canvas.addEventListener('mouseup', mouseup);
canvas.addEventListener('mouseout', mouseout);
canvas.addEventListener('wheel', mouseWheel);
range.onchange = function() {
app.WIDTH = app.HEIGHT = this.value;
app.scrollbars.left = 0;
app.scrollbars.top = 0;
app.draw();
};
// an initial drawing
app.draw();
canvas {border: 1px solid;}
span{font-size: .8em;}
<canvas id="canvas" width="200" height="150"></canvas>
<span>
change the total area size
<input type="range" min="250" max="5000000" steps="250" value="5000" id="range" />
</span>
Main advantages :
no limitation for the size of your drawing areas.
you can customize your scrollbars as you wish.
you can control when the scrollbars are enable or not.
you can get the visible area quite easily.
Main caveats:
a bit more code than the CSS solution...
no really, that's a lot of code...
A third way I wrote some time ago for an other question took advantage of the ability to draw an other canvas with ctx.drawImage(). It has its own caveats and advantages, so I let you pick the one you need, but this last one also had a drag and slide feature which can be useful.
So your node drawings don't fit on the canvas size?
You can easily "shrink" your content to fit the visible canvas with just 1 command!
The context.scale(horizontalRescale,verticalRescale) command will shrink every following drawing by your specified horizontalRescale & verticalRescale percentages.
An Important note: You must make horizontalRescale,verticalRescale the same value or your content will be distorted.
The nice thing about using context.scale is that you don't have to change any of the code that draws your nodes ... canvas automatically scales all those nodes for you.
For example, this code will shrink your nodes to 80% of their original size:
var downscaleFactor= 0.80;
context.scale( downscaleFactor, downscaleFactor );
Rather than go through your 200+ lines of code, I leave it to you to calculate downscaleFactor.

Setting a correct angular velocity to a canvas object

I am building a space shooter game and would like the ship to fire rockets at the direction of the cursor. Therefore, I grab the radian value of the angle it should fire at, multiply it by the ship's speed and set it's x and y velocities respectively.
I have this as a Bullet class:
function Bullet(x, y) {
this.x = x;
this.y = y;
this.rotation = 0;
this.width = 6;
this.height = 3;
this.color = utils.getRandomColor();
this.speed = 80;
}
And here is the function which updates the movement of all instances of the bullet class:
function drawBullet(bullet) {
var dx = mouse.x - bullet.x,
dy = mouse.y - bullet.y,
angle = Math.atan2(dy, dx);
bullet.vx = Math.cos(angle) * bullet.speed;
bullet.vy = Math.sin(angle) * bullet.speed;
bullet.x += bullet.vx;
bullet.y += bullet.vy;
bullet.draw(ctx);
}
It starts okay, going in the right direction and velocity and stuff. But as soon as it reaches the mouse, it stops dead there and starts flickering. NOW, I realise that this is because of the way I am getting the angle, using the mouse position as a value - the problem is that I can't figure out a way to use just the angle for the velocity, not the distance to the mouse position. So it doesn't slow down.
All suggestions are welcome, thanks in advance!
If you don't need homing missile type behavior just pass the mouse coordinates when you create the bullet.
Example:
new Bullet(shooterX, shooterY, mouseX, mouseY)
I included an over engineered stack snippet but the relevant part is below.
var Bullet = function(x,y,tx,ty){
this.speed = 15;
this.x = x;
this.y = y;
var radians = Math.atan2(ty-y, tx-x);
// we now have our velX and velY we can just refer to
this.velX = Math.cos(radians) * this.speed;
this.velY = Math.sin(radians) * this.speed;
}
Bullet.prototype.update = function(){
// just update by our previous calculated velX and velY.
this.x += this.velX;
this.y += this.velY;
};
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d"),
width = 250,
height = 250,
output = document.getElementById("radians"),
output2 = document.getElementById("degrees"),
cX = 0,
cY = 0,
mX = 0,
mY = 0,
bullets = [];
canvas.width = width;
canvas.height = height;
canvas.addEventListener("mousemove", function (e) {
mX = e.pageX;
mY = e.pageY;
});
var Ball = function (x, y, radius, color) {
this.x = x || 0;
this.y = y || 0;
this.radius = radius || 10;
// makes our x and y the center of the circle.
this.x = (this.x-this.radius/2);
this.y = (this.y-this.radius/2);
// how far out do we want the point
this.pointLength = 50;
this.px = 0;
this.py = 0;
this.color = color || "rgb(255,0,0)";
}
Ball.prototype.shoot = function(tx, ty){
bullets.push(new Bullet(this.x, this.y, tx, ty));
}
Ball.prototype.update = function (x, y) {
// get the target x and y
this.targetX = x;
this.targetY = y;
var x = this.x - this.targetX,
y = this.y - this.targetY,
radians = Math.atan2(y,x);
this.px = this.x - this.pointLength * Math.cos(radians);
this.py = this.y - this.pointLength * Math.sin(radians);
// -y will make 0 the top, y will 0 us at the bottom.
output.textContent = radians;
output2.textContent = radians/Math.PI * 180
};
Ball.prototype.render = function () {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "rgb(0,0,255)";
ctx.beginPath();
ctx.moveTo(this.x, this.y);
ctx.lineTo(this.px, this.py);
ctx.closePath();
ctx.stroke();
};
var Bullet = function(x,y,tx,ty){
this.speed = 15;
this.x = x;
this.y = y;
var radians = Math.atan2(ty-y, tx-x);
this.velX = Math.cos(radians) * this.speed;
this.velY = Math.sin(radians) * this.speed;
}
Bullet.prototype.update = function(){
this.x += this.velX;
this.y += this.velY;
};
Bullet.prototype.render = function(){
ctx.fillStyle = '#000';
ctx.beginPath();
ctx.arc(this.x, this.y, 2, 0, Math.PI * 2);
ctx.closePath();
ctx.fill();
};
var ball1 = new Ball(width/2, height/2, 10);
canvas.addEventListener("click", function (e) {
ball1.shoot(e.pageX, e.pageY);
});
function render() {
ctx.clearRect(0, 0, width, height);
ball1.update(mX, mY);
ball1.render();
bullets.forEach(function(b){
b.update();
b.render();
});
requestAnimationFrame(render);
}
render();
ol{list-style:none;}
<canvas id="canvas"></canvas>
<div>
<ol>
<li>
<span>Radians : </span><span id="radians"></span>
</li>
<li>
<span>Degrees : </span><span id="degrees"></span>
</li>
</ol>
</div>
Add a new property on bullet that stores the angle of motion, initialize it to -1. Then, on the very first drawBullet call, check if it has been initialized first. If not, set the angle...
function Bullet(x, y) {
this.x = x;
this.y = y;
this.rotation = 0;
this.width = 6;
this.height = 3;
this.color = utils.getRandomColor();
this.speed = 80;
this.angle = -1; // New, angle property initialized to -1
}
function drawBullet(bullet) {
if (bullet.angle === -1) { // Only pull the mouse cursor and get an angle
var dx = mouse.x - bullet.x, // If it hasn't already done so.
dy = mouse.y - bullet.y,
angle = Math.atan2(dy, dx);
bullet.angle = angle;
}
bullet.vx = Math.cos(bullet.angle) * bullet.speed; // Re-use the angle value.
bullet.vy = Math.sin(bullet.angle) * bullet.speed;
bullet.x += bullet.vx;
bullet.y += bullet.vy;
bullet.draw(ctx);
}

HTML5 canvas drawing on background scalling

i have problem i have background image and changing it scale and position with mousewheel and can drawing with mousedown and mousemove events. me example: http://jsfiddle.net/74MCQ/ Now see first drawing and second zoom we don't see drawing lines. I need make like a paint if drawing on me select position and if zoom i need see equal position with equal zoom scale.
You need a way to store the drawings of your user, either within another canvas, or by storing coordinates.
I suggest you store coordinates, below here's some code that will store the lines within an array, each line being an array of coordinates like : [x0, y0, x1, y1, x2, y2, ... ].
Edit : now i simplified the things, the coordinates are stored relative to the center of canvas.
See the fiddle, it is mostly working.
fiddle :
http://jsfiddle.net/gamealchemist/74MCQ/4/
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
var x = evt.clientX - rect.left;
var y = evt.clientY - rect.top;
var sx = (x-cw/2)/scale;
var sy = (y-ch/2)/scale;
return {
x: x,
y: y,
sx : sx,
sy:sy
};
}
/****** PAINT ******/
var isDrawing = false;
var color = "#000000";
var brushWidth = 10;
//var previousEvent = false;
ctx.strokeStyle = '#000000';
var currentLine = null;
var allLines = [];
$("#canvas").mousedown(function (e) {
var mousePos = getMousePos(canvas, e);
ctx.moveTo(mousePos.x, mousePos.y);
isDrawing = true;
if (currentLine) allLines.push(currentLine);
currentLine = [];
currentLine.push(mousePos.sx, mousePos.sy);
});
$("#canvas").mouseup(function () {
isDrawing = false;
if (currentLine) allLines.push(currentLine);
currentLine = null;
});
$("#canvas").mouseout(function () {
isDrawing = false;
if (currentLine) allLines.push(currentLine);
currentLine = null;
});
$("#canvas").mousemove(function (e) {
if (isDrawing === true) {
var mousePos = getMousePos(canvas, e);
currentLine.push(mousePos.sx, mousePos.sy);
//paint tools, effects
ctx.lineWidth = 10;
ctx.strokeStyle = color;
ctx.shadowBlur = 1;
ctx.shadowColor = 'rgb(0, 0, 0)';
ctx.lineTo(mousePos.x, mousePos.y);
ctx.stroke();
}
});
function drawStoredLines() {
var thisLine;
for (var i = 0; i < allLines.length; i++) {
thisLine = allLines[i];
drawLine(thisLine);
}
}
function drawLine(ptArray) {
if (ptArray.length <= 2) return;
ctx.beginPath();
ctx.moveTo(ptArray[0], ptArray[1]);
for (var p = 2; p < ptArray.length; p += 2) {
ctx.lineTo(ptArray[p], ptArray[p + 1]);
}
ctx.lineWidth = 10;
ctx.strokeStyle = color;
ctx.shadowBlur = 1;
ctx.shadowColor = 'rgb(0, 0, 0)';
ctx.stroke();
}
Notice that i couldn't resist reducing your 175 lines code to select the scale to a 25 lines one :-)
var zoomSteps = [0.1, 0.2, 0.4, 0.6, 0.8, 1.0, 1.5, 2.0, 3.0, 4.0];
var zoomIndex = zoomSteps.indexOf(1);
function doScroll(e) {
e = window.event || e;
var delta = Math.max(-1, Math.min(1, (e.wheelDelta || -e.detail)));
zoomIndex = zoomIndex + delta;
if (zoomIndex < 0) zoomIndex = 0;
if (zoomIndex >= zoomSteps.length) zoomIndex = zoomSteps.length - 1;
scale = zoomSteps[zoomIndex];
imageWidthZoomed = imageWidth * scale;
imageHeightZoomed = imageHeight * scale;
var mousePos = getMousePos(canvas, e);
draw(mousePos.x, mousePos.y, scale);
}

Is KineticJS capable of performing smooth animations with many objects using the move method?

I am trying to animate many objects to a canvas using KineticJS. I am using the built-in move method on every frame. It is known that redrawing a layer is an expensive operation which can cause performance issues, so I am calling layer.draw() only after each move operations has already been executed. Despite this, the more objects I animate, the poor the performance becomes and the end result is a sluggish animation.
To compare the KineticJS performance against the native canvas, I prepared two demos that do the same thing - bouncing balls in a canvas of 500x500. The first one is using the native canvas. It just clears the canvas on each frame and draws the balls. The second one uses KineticJS and once the image objects are created, it uses the move method to move them.
It is obvious that while the native demo performs the same with 10, 100 and 1000 balls, the performance of KineticJS demo is strongly affected by the number of balls. With 1000, it is just unusable. There are many optimizations that can be made to both examples, including using requestAnimationFrame for animation loop or using the built-in Animation object for KineticJS, but these will not change the performance of the demos much.
So here are the two demos. First, the native one - http://jsfiddle.net/uxsLN/1/
(function() {
window.addEventListener('load', loaded, false);
function loaded() {
img = new Image();
img.onload = canvasApp;
img.src = 'ball.png';
}
function canvasApp() {
var theCanvas = document.getElementById("canvas");
var context = theCanvas.getContext("2d");
function drawScreen() {
context.clearRect(0, 0, theCanvas.width, theCanvas.height);
context.strokeStyle = '#000000';
context.strokeRect(1, 1, theCanvas.width - 2, theCanvas.height - 2);
context.fillStyle = "#000000";
var ball;
for (var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.x += ball.xunits;
ball.y += ball.yunits;
context.drawImage(img, ball.x, ball.y);
if (ball.x + ball.radius * 2 > theCanvas.width || ball.x < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if (ball.y + ball.radius * 2 > theCanvas.height || ball.y < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
}
}
}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
for (var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var ball = {
x : (theCanvas.width - radius) / 2,
y : (theCanvas.height - radius) / 2,
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed
}
balls.push(ball);
}
function gameLoop() {
window.setTimeout(gameLoop, 20);
drawScreen()
}
gameLoop();
}
})();
Next, KineticJS - http://jsfiddle.net/MNpUX/
(function() {
window.addEventListener('load', loaded, false);
function loaded() {
img = new Image();
img.onload = canvasApp;
img.src = 'ball.png';
}
function canvasApp() {
var stage = new Kinetic.Stage({
container : 'container',
width : 500,
height : 500
});
var layer = new Kinetic.Layer();
stage.add(layer);
rect = new Kinetic.Rect({
x : 0,
y : 0,
width : stage.getWidth(),
height : stage.getHeight(),
fill : '#EEEEEE',
stroke : 'black'
});
layer.add(rect);
function drawScreen() {
var ball;
for ( var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.obj.move(ball.xunits, ball.yunits);
if (ball.obj.getX() + ball.radius * 2 > stage.getWidth() || ball.obj.getX() < 0) {
ball.angle = 180 - ball.angle;
updateBall(ball);
} else if (ball.obj.getY() + ball.radius * 2 > stage.getHeight() || ball.obj.getY() < 0) {
ball.angle = 360 - ball.angle;
updateBall(ball);
}
}
layer.draw();
}
function updateBall(ball) {
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
for ( var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var obj = new Kinetic.Image({
image : img,
x : (stage.getWidth() - radius) / 2,
y : (stage.getHeight() - radius) / 2
});
layer.add(obj);
var ball = {
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed,
obj : obj
};
balls.push(ball);
}
function gameLoop() {
window.setTimeout(gameLoop, 20);
drawScreen()
}
gameLoop();
}
})();
So the question is - do I miss something about KineticJS or it is just not built for such a purpose?
You can gain a little speed by:
Turning listening off on the stage.
Using layer.drawScene instead of layer.draw. (drawScene doesn't also redraw the hit scene).
Reducing the ball count to 500 (the effect looks pretty much the same).
If your design permits, use a custom Kinetic.Shape to get "closer to the metal".
The Kinetic.Shape gives you a wrapped Context on which you can run native Context commands.
Using Shape, you'll get magnitudes better results because there's only 1 object being managed.
Here's code and a Fiddle: http://jsfiddle.net/m1erickson/AVJyr/
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<script src="http://d3lp1msu2r81bx.cloudfront.net/kjs/js/lib/kinetic-v4.7.2.min.js"></script>
<style>
body{padding:20px;}
#container{
border:solid 1px #ccc;
margin-top: 10px;
width:500px;
height:500px;
}
</style>
<script>
$(function(){
var stage = new Kinetic.Stage({
container: 'container',
width: 500,
height: 500,
listening:false
});
var layer = new Kinetic.Layer();
stage.add(layer);
//
var cw=stage.getWidth();
var ch=stage.getHeight();
var numBalls = 1000;
var maxSize = 8;
var minSize = 5;
var maxSpeed = maxSize + 5;
var balls = [];
var radius = 24;
// this is a custom Kinetic.Shape
var shape;
for (var i = 0; i < numBalls; i++) {
var speed = maxSpeed - radius;
var angle = Math.floor(Math.random() * 360);
var radians = angle * Math.PI / 180;
var ball = {
x : (cw-radius)/2,
y : (ch-radius)/2,
radius : radius,
speed : speed,
angle : angle,
xunits : Math.cos(radians) * speed,
yunits : Math.sin(radians) * speed
}
balls.push(ball);
}
// load the ball image and create the Kinetic.Shape
img = new Image();
img.onload=function(){
shape=new Kinetic.Shape({
x: 0,
y: 0,
width:500,
height:500,
draggable: true,
drawFunc: function(context) {
context.beginPath();
var ball;
for (var i = 0; i < balls.length; i++) {
ball = balls[i];
ball.x += ball.xunits;
ball.y += ball.yunits;
context.drawImage(img, ball.x, ball.y);
if (ball.x+ball.radius*2>cw || ball.x<0) {
ball.angle = 180 - ball.angle;
} else if (ball.y+ball.radius*2>ch || ball.y<0) {
ball.angle = 360 - ball.angle;
}
ball.radians = ball.angle * Math.PI / 180;
ball.xunits = Math.cos(ball.radians) * ball.speed;
ball.yunits = Math.sin(ball.radians) * ball.speed;
}
context.fillStrokeShape(this);
},
});
layer.add(shape);
// GO!
gameLoop();
}
img.src = 'http://users-cs.au.dk/mic/dIntProg/e12/uge/4/Projekter/bouncingballs/assignment/ball.png';
// RAF used to repeatedly redraw the custom shape
function gameLoop(){
window.requestAnimationFrame(gameLoop);
layer.clear();
shape.draw();
}
}); // end $(function(){});
</script>
</head>
<body>
<div id="container"></div>
</body>
</html>

Drag & drop image on canvas image

I want to drag & drop text above image. For that I am using canvas. I am using this code
<img id="scream" src="http://127.0.0.1/demo/images.jpg" alt="The Scream" style="display:none;" width="220" height="277"><p>Canvas:</p>
<canvas id="canvas" width="300" height="300" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
var c=document.getElementById("canvas");
var ctx1=c.getContext("2d");
var img=document.getElementById("scream");
ctx1.drawImage(img,10,10);
var canvas;
var ctx;
var x = 75;
var y = 50;
var dx = 5;
var dy = 3;
var WIDTH = 400;
var HEIGHT = 300;
var dragok = false,
text = "Hey there im moving!",
textLength = (text.length * 14)/2;
function rect(x,y,w,h) {
ctx.font = "14px Arial";
ctx.strokeText("Hey there im a moving!!", x, y);
}
function clear() {
ctx.clearRect(0, 0, WIDTH, HEIGHT);
}
function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
return setInterval(draw, 10);
}
function draw() {
clear();
ctx.fillStyle = "#FAF7F8";
ctx.fillStyle = "#444444";
rect(x - 15, y + 15, textLength, 30);
}
function myMove(e){
if (dragok){
x = e.pageX - canvas.offsetLeft;
y = e.pageY - canvas.offsetTop;
}
}
function myDown(e){
if (e.pageX < x + textLength + canvas.offsetLeft && e.pageX > x - textLength + canvas.offsetLeft && e.pageY < y + 15 + canvas.offsetTop &&
e.pageY > y -15 + canvas.offsetTop){
x = e.pageX - canvas.offsetLeft;
y = e.pageY - canvas.offsetTop;
dragok = true;
canvas.onmousemove = myMove;
}
}
function myUp(){
dragok = false;
canvas.onmousemove = null;
}
init();
canvas.onmousedown = myDown;
canvas.onmouseup = myUp;
I either able to show image or drag & drop text but I want both, please help me where I am wrong. You can check here:- http://jsfiddle.net/FWdSv/11/
When you're clearing the canvas, you're also clearing your image.
So the easy fix is to redraw the image in your draw function:
function draw() {
clear();
ctx.drawImage(img,0,0);
ctx.fillStyle = "#FAF7F8";
ctx.fillStyle = "#444444";
rect(x - 15, y + 15, textLength, 30);
}
Alternatively:
You could display your image underneath your canvas so it's not affected when you clear the canvas.