Related
<!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.
I have a kineticjs canvas with image upload and text input, both functions are working fine but I can't get the image resize anchors to show... I need to get the image resize anchors to show "onClick" of the image.
any help is much appreciated :)
thanks in advance.
here is the js
var stage = new Kinetic.Stage({
container: 'container',
width: 375,
height: 200
});
var layer = new Kinetic.Layer();
//image loader
var imageLoader = document.getElementById('imageLoader');
imageLoader.addEventListener('change', handleImage, false);
function handleImage(e){
var reader = new FileReader();
reader.onload = function(event){
var img = new Image();
img.onload = function(){
layer.add(new Kinetic.Image({
x: 100,
y: 50,
image: img,
width: 200,
height: 130,
draggable: true
}));
text.moveToTop();
stage.draw();
};
console.log(event);
img.src = event.target.result;
};
reader.readAsDataURL(e.target.files[0]);
}
// parameters
var resizerRadius = 3;
var rr = resizerRadius * resizerRadius;
// constant
var pi2 = Math.PI * 2;
function draw(img, withAnchors, withBorders) {
// clear the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw the image
var view = img.view;
ctx.drawImage(img, 0, 0, img.width, img.height, view.left, view.top, view.width, view.height);
// optionally draw the draggable anchors
if (withAnchors) {
drawDragAnchor(view.left, view.top);
drawDragAnchor(view.left + view.width, view.top);
drawDragAnchor(view.left + view.width, view.top + view.height);
drawDragAnchor(view.left, view.top + view.height);
}
// optionally draw the connecting anchor lines
if (withBorders) {
ctx.beginPath();
ctx.rect(view.left, view.top, view.width, view.height);
ctx.stroke();
}
drawText();
}
function drawDragAnchor(x, y) {
ctx.beginPath();
ctx.arc(x, y, resizerRadius, 0, pi2, false);
ctx.closePath();
ctx.fill();
}
function drawText(){
var x = 40,
y = 100;
ctx.font = "bold 20px sans-serif";
ctx.fillStyle = "black";
ctx.fillText($("#textBox").val(), x, y);
}
// -------------------------------------------
// - Hit Testing -
// -------------------------------------------
// return 0,1,2, or 3 if (x,y) hits the respective anchor
// of the given view.
// return -1 if no anchor hit.
function anchorHitTest(view, x, y) {
var dx, dy;
x -= view.left;
y -= view.top;
// top-left
dx = x;
dy = y;
if (dx * dx + dy * dy <= rr) return (0);
// top-right
dx = x - view.width;
dy = y;
if (dx * dx + dy * dy <= rr) return (1);
// bottom-right
dx = x - view.width;
dy = y - view.height;
if (dx * dx + dy * dy <= rr) return (2);
// bottom-left
dx = x;
dy = y - view.height;
if (dx * dx + dy * dy <= rr) return (3);
return (-1);
}
// return true if (x,y) lies within the view
function hitImage(view, x, y) {
x -= view.left;
y -= view.top;
return (x > 0 && x < view.width && y > 0 && y < view.height);
}
// -------------------------------------------
// - Mouse -
// -------------------------------------------
var mousePos = {
x: 0,
y: 0
};
var draggingImage = false;
var startX, startY;
var isDown = false;
var currentImg = null;
var draggingResizer;
function updateMousePos(e) {
var canvasOffset = $("#canvas").offset();
var offsetX = canvasOffset.left;
var offsetY = canvasOffset.top;
updateMousePos = function (e) {
mousePos.x = parseInt(e.clientX - offsetX);
mousePos.y = parseInt(e.clientY - offsetY);
};
return updateMousePos(e);
}
function handleMouseDown(e) {
updateMousePos(e);
// here you could make a loop to see which image / anchor was clicked
draggingResizer = anchorHitTest(img.view, mousePos.x, mousePos.y);
draggingImage = draggingResizer < 0 && hitImage(img.view, mousePos.x, mousePos.y);
//
if (draggingResizer<0 && !draggingImage) return;
startX = mousePos.x;
startY = mousePos.y;
currentImg = img;
}
function handleMouseUp(e) {
if (!currentImg) return;
draggingResizer = -1;
draggingImage = false;
draw(currentImg, true, false);
currentImg = null;
}
function handleMouseOut(e) {
handleMouseUp(e);
}
function handleMouseMove(e) {
if (!currentImg) return;
updateMousePos(e);
var view = currentImg.view;
if (draggingResizer > -1) {
var oldView = {
left: view.left,
top: view.top,
width: view.width,
height: view.height
};
// resize the image
switch (draggingResizer) {
case 0:
cl('ttoo');
//top-left
view.left = mousePos.x;
view.top = mousePos.y;
view.width = oldView.left + oldView.width - mousePos.x;
view.height = oldView.top + oldView.height - mousePos.y;
break;
case 1:
//top-right
// view.left unchanged
view.top = mousePos.y;
view.width = mousePos.x - oldView.left;
view.height = oldView.top + oldView.height - mousePos.y;
break;
case 2:
//bottom-right
view.width = mousePos.x - oldView.left;
view.height = mousePos.y - oldView.top;
break;
case 3:
//bottom-left
view.left = mousePos.x;
view.width = oldView.left + oldView.width - mousePos.x;
view.height = mousePos.y - (oldView.top);
break;
}
if (view.width < 25) view.width = 25;
if (view.height < 25) view.height = 25;
// redraw the image with resizing anchors
draw(currentImg, true, true);
} else if (draggingImage) {
imageClick = false;
// move the image by the amount of the latest drag
var dx = mousePos.x - startX;
var dy = mousePos.y - startY;
view.left += dx;
view.top += dy;
// reset the startXY for next time
startX = mousePos.x;
startY = mousePos.y;
// redraw the image with border
draw(currentImg, false, true);
}
}
var text = new Kinetic.Text({
x: 20,
y: 30,
text: '',
fontSize: '30',
fontFamily: 'Calibri',
fill: 'black',
draggable: true
});
stage.add(layer);
layer.add(text);
document.getElementById("textBox").addEventListener("keyup", function () {
text.setText(this.value);
layer.draw();
}, true);
document.getElementById("textSize").addEventListener("change", function () {
var size = this.value;
text.fontSize(size);
layer.draw();
}, true);
document.getElementById("fontFamily").addEventListener("change", function () {
var font = this.value;
text.fontFamily(font);
layer.draw();
}, true);
document.getElementById("fontStyle").addEventListener("change", function () {
var style = this.value;
text.fontStyle(style);
layer.draw();
}, true);
document.getElementById("fill").addEventListener("change", function () {
var colour = this.value;
text.fill(colour);
layer.draw();
}, true);
$("#canvas").mousedown(function (e) {
handleMouseDown(e);
});
$("#canvas").mousemove(function (e) {
handleMouseMove(e);
});
$("#canvas").mouseup(function (e) {
handleMouseUp(e);
});
$("#canvas").mouseout(function (e) {
handleMouseOut(e);
});
// utility
function cl() {
console.log.apply(console, arguments);
}
can provide jsFiddle if needed :)
You're trying to mix KineticJS with html canvas drawing commands.
That combination doesn't work because KineticJS does its magic by taking over the canvas--leaving no ability to call native canvas commands like context.beginPath.
// these 2 don't play together
... new Kinetic.Image ...
... ctx.beginPath ...
Anyway, Here's the answer to your question (in case you choose KineticJS for your project)
Kinetic.Image can be asked to execute a function when the image is clicked like this:
var image=new Kinetic.Image({
x: 100,
y: 50,
image: img,
width: 200,
height: 130,
draggable: true
}));
image.on("click",function(){
// The image was clicked
// Show your anchors now
});
layer.add(image);
[ Addition: Example of Kinetic.Image resizing ]
I don't like the overhead and complexity of maintaining anchors to resize Kinetic.Images.
Here's an example that lets you drag on the right side of the image to scale it proportionally:
http://jsfiddle.net/m1erickson/p8bpC/
You could modify this code to add cosmetic resizing grabbers (the grabbers are not necessary, but if you prefer the "anchor" look, you can add them).
You can refer to this question, the answers are guided and constructive, and contain a jsfiddle with the exact same behavior that you need.
Kinetic JS - how do you hide all the anchors for a given group ID
I'm pretty new to this and I got the animation working by watching a youtube tutorial.
Here is a canvas animation of a keyboard controlled car.
http://jsfiddle.net/unn9P/
canvas = document.getElementById('canvas');
c = canvas.getContext('2d');
c.clear = function() {
this.clearRect(0,0,1500,1500) };
function wait(fn) {
window.setTimeout(fn, 250) }
function repeat(fn) {
if (requestAnimationFrame) {
var advance = function() {fn(); requestAnimationFrame(advance);};
requestAnimationFrame(advance);
} else window.setInterval(fn, 50);
}
var dx = 0, dy = 0, mousex = 0, mousey=0, mouseclicks = 0;
document.onkeydown = function(e) {
var key = e.keyCode;
if (key == 37) dx=-1;
else if (key == 38) dy=-1;
else if (key == 39) dx=1;
else if (key == 40) dy=1;
else return true;
return false;
};
document.onkeyup = function(e) {
var key = e.keyCode;
if (key == 37 || key == 39) dx=0;
else if (key == 38 || key == 40) dy=0;
else return true;
return false;
};
canvas.onmousemove = function(e) {
var rect = canvas.getBoundingClientRect();
mousex = e.clientX - rect.left;
mousey = e.clientY - rect.top;
};
canvas.onmousedown = function(e) {mouseclicks++;};
a = new Image();
a.src = 'http://o.ooli.ca/car_top.png';
wait(function(){
x = 50;
y = 50;
angle = 0;
repeat(function() {
angle = angle + dx;
x = x - dy * Math.cos(angle * Math.PI / 180);
y = y - dy * Math.sin(angle * Math.PI / 180);
c.clear();
c.translate(x, y);
c.rotate(angle * Math.PI / 180);
c.translate(-37, -19);
c.drawImage(a, 0, 0);
c.setTransform(1, 0, 0, 1, 0, 0); //reset
});
});
Now I would like to add trail behind the car as it moves along and create a reset button inside the canvas which can help me clear the trail, and bring the car to its initial position.
I have read some tutorials but i can't seem to find what I want.
Is there any idea/suggestion on how I should do this?
Simply record your points when moving:
Modified fiddle here
repeat(function () {
angle = angle + dx;
x = x - dy * Math.cos(angle * Math.PI / 180);
y = y - dy * Math.sin(angle * Math.PI / 180);
/// record point
pts.push([x, y]);
c.clear();
/// render points (see below)
renderTrail(pts, c);
c.translate(x, y);
...
Then have a function to render the recorded points:
function renderTrail(pts, c) {
if (pts.length > 1) {
c.beginPath();
c.moveTo(pts[0][0], pts[0][1]);
for(var i = 1, pt; pt = pts[i]; i++) {
c.lineTo(pt[0], pt[1]);
}
c.stroke();
}
}
To reset just clear the point array:
pts = [];
You can do this on a mouse-click event on the canvas where you chose to draw your button or just put a html button on top of canvas (above it, literally on top will reduce the performance of canvas).
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;
});
}
So, another post on here REALLY helped me prior from this link!
It got me to zoom in neatly, but what I'm really looking for in my panning is a mouse drag (I will be working with a touch screen, so dragging to move is important) No need to use Touch classes, I have done dragging events with Mouse classes before and find it easier to me.
My problem here is that my neat zoom is linked to this MOUSE_MOVE panning, and I want to know a clean way to change my panning to a drag event, and still not have my panning go past my image constraints.
bg_mc- is my background image to move and zoom.
My code:
import com.greensock.*;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
this.mask = mask_mc;
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
function mousemove(e:MouseEvent) {
if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) {
if (bg_mc.width>mask_mc.width) {//Avoids Scrolling if image is under mask area width
percX = mask_mc.mouseX/mask_mc.width;
}
if (bg_mc.height>mask_mc.height) {
//Avoids Scrolling if image is under mask area height
percY = mask_mc.mouseY/mask_mc.height;
}
destX = -(bg_mc.width-mask_mc.width)*percX;
destY = -(bg_mc.height-mask_mc.height)*percY;
TweenMax.to(bg_mc,.5,{x:destX,y:destY});
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 4, null, resumeMouseMove);
}
function decreaseSize(event:MouseEvent):void{
stopMouseMove();
scaleAroundMouse(bg_mc, 1, null, resumeMouseMove);
}
function stopMouseMove():void {
stage.removeEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
function resumeMouseMove():void {
stage.addEventListener(MouseEvent.MOUSE_MOVE,mousemove);
}
Any help I can get would be great!
On a mousedown or whatever listener you want to start dragging, use the MovieClip.StartDrag() function to allow dragging, and the StopDrag() to stop it.
While you could use the built in startDrag (or startTouchDrag) and stopDrag methods, it will not play nice with your zoom in/out function.
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown); //add the listener to the bg directly
function mouseDown(e:MouseEvent) {
// if (mask_mc.hitTestPoint(stage.mouseX,stage.mouseY,false)) { //this isn't needed if you add the listener directly to bg_mc
stage.addEventListener(MouseEvent.MOUSE_UP,mouseUp);
bg_mc.startDrag(false, getBoundsRect()); //see the code below for the getBoundsRect() function
}
function mouseUp(e:MouseEvent):void {
bg_mc.stopDrag();
stage.removeEventListener(MouseEvent.MOUSE_UP,mouseUp);
}
You'd be better served to have a custom drag function, like below:
import com.greensock.*;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
import flash.events.Event;
import flash.geom.Point;
bg_mc.doubleClickEnabled = true;
//Variables
var percX:Number;
var percY:Number;
var destX:Number;
var destY:Number;
//Image panned and masked
bg_mc.mask = mask_mc;
//this function generates a bounds rectangle that would keep bg_mc edges from going outside of mask_mc area
function getBoundsRect():Rectangle {
return new Rectangle((mask_mc.x + mask_mc.width) - bg_mc.width, (mask_mc.y + mask_mc.height) - bg_mc.height, bg_mc.width - mask_mc.width, bg_mc.height - mask_mc.height);
}
var isZoomed:Boolean = false; //a var to keep track of whether your zoomed in or out
var isDragging:Boolean = false; //a var to keep track of whether the bg is being dragged
var tmpMousePoint:Point = new Point(); //this stores the mouse coordinates on the mouse down, to compare later on the mouse up to see if the mouse moved
var decay:Number = .27; //make this lower for slower drag, make it 1 to turn off a smooth drag all together
var tmpMouseMoved:Boolean = false; //to keep track on mouse up whether the action was a drag or a click
var mouseMoveSensitivity:Number = 15; //how far does mouse need to move before you cancel the click event
var offset:Point = new Point(); //the offset of the initial mouse click relative to bg_mc's 0,0
bg_mc.addEventListener(MouseEvent.MOUSE_DOWN,mouseDown);
bg_mc.addEventListener(MouseEvent.CLICK,mouseClick, false, 99999); //listen with a higher priority than any other click listeners on bg_mc - this way if it's a drag, you can cancel the click event
function mouseDown(e:Event):void {
//reset these to default
isDragging = true;
tmpMouseMoved = false;
tmpMousePoint.x = mouseX; //capture the current mouse to check later if it moved (so you know the user intended to drag not click)
tmpMousePoint.y = mouseY;
offset.x = bg_mc.mouseX;
offset.y = bg_mc.mouseY;
bg_mc.addEventListener(Event.ENTER_FRAME,bgEnterFrame); //listen every frame until the mouse is released
stage.addEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function bgEnterFrame(e:Event):void {
bg_mc.x += decay * (mouseX - offset.x - bg_mc.x);
bg_mc.y += decay * (mouseY - offset.y - bg_mc.y);
var bounds:Rectangle = getBoundsRect();
switch(true){
case (bg_mc.x < bounds.x):
bg_mc.x = bounds.x;
break;
case (bg_mc.x > bounds.x + bounds.width):
bg_mc.x = bounds.x + bounds.width;
}
switch(true){
case (bg_mc.y < bounds.y):
bg_mc.y = bounds.y;
break;
case (bg_mc.y > bounds.y + bounds.height):
bg_mc.y = bounds.y + bounds.height;
}
if(Math.abs(tmpMousePoint.x - mouseX) > mouseMoveSensitivity || Math.abs(tmpMousePoint.y - mouseY) > mouseMoveSensitivity){
tmpMouseMoved = true;
}
}
function mouseUp(e:Event):void {
isDragging = false;
//remove listeners
bg_mc.removeEventListener(Event.ENTER_FRAME,bgEnterFrame);
stage.removeEventListener(MouseEvent.MOUSE_UP, mouseUp);
}
function mouseClick(e:MouseEvent):void {
trace("CLICK cap");
if(tmpMouseMoved){
trace("Kill");
e.stopImmediatePropagation(); //cancel the mouse event
}
}
//Add listeners for the imgLoader movie clip.
bg_mc.doubleClickEnabled = true;
bg_mc.addEventListener(MouseEvent.DOUBLE_CLICK, increaseSize);
bg_mc.addEventListener(MouseEvent.CLICK, decreaseSize,false,0,true);
function scaleAroundMouse(objectToScale:DisplayObject, scaleAmount:Number,
bounds:Rectangle = null, onComplete:Function = null):TweenLite {
// scaling will be done relatively
var relScaleX:Number = scaleAmount / objectToScale.scaleX;
var relScaleY:Number = scaleAmount / objectToScale.scaleY;
// map vector to centre point within parent scope
var scalePoint:Point = objectToScale.localToGlobal( new
Point(objectToScale.mouseX, objectToScale.mouseY));
scalePoint = objectToScale.parent.globalToLocal( scalePoint );
// current registered postion AB
var AB:Point = new Point( objectToScale.x, objectToScale.y );
// CB = AB - scalePoint, objectToScale vector that will scale as it runs from the centre
var CB:Point = AB.subtract( scalePoint );
CB.x *= relScaleX;
CB.y *= relScaleY;
// recaulate AB, objectToScale will be the adjusted position for the clip
AB = scalePoint.add( CB );
// set actual properties
if(bounds){
var limits:Rectangle = new Rectangle(
bounds.x + (bounds.width - (objectToScale.width * relScaleX)),
bounds.y + (bounds.height - (objectToScale.height * relScaleY)),
(objectToScale.width * relScaleX) - bounds.width,
(objectToScale.height * relScaleY) - bounds.height
);
if(AB.x < limits.x) AB.x = limits.x;
if(AB.x > limits.x + limits.width) AB.x = limits.x + limits.width;
if(AB.y < limits.y) AB.y = limits.y;
if(AB.y > limits.y + limits.height) AB.y = limits.y + limits.height;
}
TweenLite.killTweensOf(objectToScale); //need to add this so the click/double click don't compete with each other
return TweenLite.to(objectToScale,1,{onComplete: onComplete,
scaleX: scaleAmount, scaleY: scaleAmount, x: AB.x, y: AB.y});
}
function increaseSize(event:MouseEvent):void{
if(isZoomed){
scaleAroundMouse(bg_mc, 4, getBoundsRect());
isZoomed = false;
}
}
function decreaseSize(event:MouseEvent):void{
if(!isZoomed){
scaleAroundMouse(bg_mc, 1, getBoundsRect());
isZoomed = true;
}
}