Raphael seems to lack shape hierarchies?
I can't create a smaller circle "attached" to a larger circle, and know that it will be scaled/translated when the larger one is.
Similarly, if i put elements into a set, the drag handler connects to shapes individually and in its callbacks i have no handle on the set.
Am i overlooking something?
This the current behaviour as Raphael does not create any real element for a "set".
If you want to enable Drag'nDrop on a set, you can use the following code :
Raphael.el.set_drag = function (aSet) {
// Enable drag'n drop in a Set
var startAll = function () {
// storing original coordinates
for (var i in this.set.items) {
var comp = this.set.items[i];
try {
comp.attr({opacity: 0.3});
} catch (ex) {;}
if (comp.type == "path") {
comp.ox = comp.getBBox().x;
comp.oy = comp.getBBox().y;
}
else {
comp.ox = comp.attrs.cx || comp.attrs.x;
comp.oy = comp.attrs.cy || comp.attrs.y;
}
}
},
moveAll = function (dx, dy) {
for (var i in this.set.items) {
var comp = this.set.items[i];
if (comp.attrs.cx) // ellipse
comp.attr({cx: comp.ox + dx, cy: comp.oy + dy});
else if (comp.attrs.x)
comp.attr({x: comp.ox + dx, y: comp.oy + dy});
else // path
comp.translate(comp.ox - comp.getBBox().x + dx, comp.oy - comp.getBBox().y + dy);
}
},
upAll = function () {
for (var i in this.set.items) {
var comp = this.set.items[i];
if (comp.attrs.cx) // ellipse
comp.attr({cx: comp.ox, cy: comp.oy + dy});
else if (comp.attrs.x)
comp.attr({x: comp.ox, y: comp.oy + dy});
else // path
comp.translate(comp.ox , comp.oy - comp.getBBox().y + dy);
this.set.items[i].attr({opacity: 1});
}
};
this.set = aSet; //create a "set" property on the element
this.drag(moveAll,startAll,upAll);
return this;
}
// Create your elements
var first_element = paper.rect(10,10,100,50).attr({fill:'#f00'});
var second_element = paper.rect(30,300,100,50).attr({fill:'#0f0'});
// Add elements to your set
var myset = paper.set();
myset.push(first_element);
myset.push(second_element);
// Add handler on the elements
first_element.set_drag(myset);
second_element.set_drag(book_set);
Related
I've the below code that is functioning properly, create the path, and rotate it upon the click.
I want to rotate the path about specific point, when i use the rotate(45 50 50) below instead of the rotate(x) i get this error: VM267:85 Uncaught SyntaxError: missing ) after argument list what shall I do?
Note NOt interested to use any ready library to handle the task, need to so it using the standard API only. thanks
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
svg = SVG('svg');
svg.setAttribute("width", "100%");
svg.setAttribute("height", "100%");
// svg.width='50em'; // Not working
document.body.appendChild(svg);
var bbox = svg.getBoundingClientRect();
var center = {
x: bbox.left + bbox.width/2,
y: bbox.top + bbox.height/2
};
class myPath {
constructor(cx,cy) {
this.path=SVG('path');
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
var d="M" + cx + " " + cy;
d = d + "L175 120 L125 120 Z";
this.path.setAttribute("d", d);
this.path.setAttribute("fill", "#F7931E");
this.path.addEventListener("click",this,false);
}
get draw(){
return this.path;
}
}
myPath.prototype.rotate = function(x) {
/*
var path = this.path.getBoundingClientRect();
var Pc = {
x: bbox.left + bbox.width/2,
y: bbox.top + bbox.height/2
}; */
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(x));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.animate = function() {
self = this.path;
self.transform.baseVal.appendItem(this.rotate(5));
};
myPath.prototype.handleEvent= function(evt){
self = evt.target;
console.log(self.getAttribute('d'));
self.move = setInterval(()=>this.animate(),100);
}
svg.appendChild(new myPath(center.x,center.y).draw);
rotate(45 50 50) is the format for the transform XML attribute. For example:
<path d="..." transform="rotate(45 50 50)" .../>
But you are using the Javascript rotate() function on the SVGTransform object. JS functions require commas between parameters. Try:
rotate(45, 50, 50)
https://developer.mozilla.org/en/docs/Web/API/SVGTransform
I was able to solve it using translate(<x>, <y>) rotate(<a>) translate(-<x>, -<y>) as per this link
var NS="http://www.w3.org/2000/svg";
var SVG=function(el){
return document.createElementNS(NS,el);
}
svg = SVG('svg');
svg.setAttribute("width", "100%");
svg.setAttribute("height", "100%");
svg.setAttribute("fill", "green");
document.body.appendChild(svg);
bbox = svg.getBoundingClientRect();
center = {
x: this.bbox.left + this.bbox.width/2,
y: this.bbox.top + this.bbox.height/2
};
class myPath {
constructor(cx,cy) {
this.path=SVG('path');
// https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/d
// https://developer.mozilla.org/en-US/docs/Web/SVG/Tutorial/Paths
var d="M" + cx + " " + cy;
d = d + "h75 v75 h-75 z";
this.path.setAttribute("d", d);
this.path.setAttribute("fill", "#F7931E");
this.path.addEventListener("click",this,false);
this.Pbox = svg.getBoundingClientRect();
this.Pc = {
x: this.Pbox.left + this.Pbox.width/2,
y: this.Pbox.top + this.Pbox.height/2
};
}
get draw(){
return this.path;
}
}
myPath.prototype.rotate = function(x) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().rotate(x));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.move = function(x,y) {
return svg.createSVGTransformFromMatrix(svg.createSVGMatrix().translate(x,y));
// https://developer.mozilla.org/en/docs/Web/SVG/Attribute/transform
}
myPath.prototype.animate = function() {
self = this.path;
self.transform.baseVal.appendItem(this.move(this.Pc.x,this.Pc.y));
self.transform.baseVal.appendItem(this.rotate(5));
self.transform.baseVal.appendItem(this.move(-this.Pc.x,-this.Pc.y));
};
myPath.prototype.handleEvent= function(evt){
self = evt.target;
console.log(self.getAttribute('d'));
self.move = setInterval(()=>this.animate(),100);
}
svg.appendChild(new myPath(center.x,center.y).draw);
I have been trying to save data/object from HTML5 Canvas' drawing feature, how would I save to mySQL table by x and y coordinate points, instead of converting it into a JPEG or an image file?.
The goal is to save the strokes as data file instead of image file. How can I do this?
var canvas = document.getElementById('canvas'),
coord = document.getElementById('coord'),
ctx = canvas.getContext('2d'), // get 2D context
imgCat = new Image();
/*********** draw image *************/
imgCat.src = 'http://c.wearehugh.com/dih5/openclipart.org_media_files_johnny_automatic_1360.png';
imgCat.onload = function() { // wait for image load
ctx.drawImage(imgCat, 0, 0); // draw imgCat on (0, 0)
};
/*********** handle mouse events on canvas **************/
var mousedown = false;
ctx.strokeStyle = '#0000FF';
ctx.lineWidth = 2;
canvas.onmousedown = function(e) {
var pos = fixPosition(e, canvas);
mousedown = true;
ctx.beginPath();
ctx.moveTo(pos.x, pos.y);
return false;
};
canvas.onmousemove = function(e) {
var pos = fixPosition(e, canvas);
coord.innerHTML = '(' + pos.x + ',' + pos.y + ')';
if (mousedown) {
ctx.lineTo(pos.x, pos.y);
ctx.stroke();
}
};
canvas.onmouseup = function(e) {
mousedown = false;
};
/********** utils ******************/
// Thanks to http://stackoverflow.com/questions/55677/how-do-i-get-the-coordinates-of-a-mouse-click-on-a-canvas-element/4430498#4430498
function fixPosition(e, gCanvasElement) {
var x;
var y;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
} else {
x = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
}
x -= gCanvasElement.offsetLeft;
y -= gCanvasElement.offsetTop;
return {
x: x,
y: y
};
}
``
<div id="left_col">
<canvas id="canvas" width="900" height="900" style='background-image:url(http://www.robertshadbolt.com/content/01-articles/01-900x900/900x900.gif);' center cetner no-repeat></canvas>
<div id="coord" hidden></div>
Fiddle
Canvas cannot handle vector graphics in the sense it will rasterize anything drawn to it. Therefor canvas will only provide you with bitmaps (raw or encoded as an image). The canvas can only show the result of those vectors being rasterized, but can never provide them in raw form afterwards.
To obtain the effect of dealing with vectors you need to do the following:
Track and collect the points in a serializeable format such as an array with literal objects. In other words: you need to "record" these points in your mouse handlers parallel to rendering them to canvas.
You will also need to separate "strokes" (a stroke is from mouse down to mouse up, then create a new array for the next stroke etc. meaning you will need an object with two levels, one to collect stroke arrays, and one array per stroke).
When done you can serialize the array using JSON.stringify() and send to your database.
To restore, read that string back and use JSON.parse() to restore the array.
Simplified Example
// some dummy point in a serializeable format:
var points = [
{x: 10, y: 10},
{x: 20, y: 50},
{x: 100, y: 10}
];
// serialize
var str = JSON.stringify(points);
document.write("To server: " + str + "<br>");
// send str to database here...
// RESTORE
// read from database here
var restoredPoints = JSON.parse(str);
document.write("Point from restored array - x: " +
restoredPoints[1].x + " y: " +
restoredPoints[1].y);
In a more real example your object would look like (pseudo code):
var strokes = [];
onmousedown:
create new array -> strokes.push([]);
index = strokes.length - 1;
add point to current stroke -> strokes[index].push({x:x, y:y});
onmousemove:
add point to current stroke -> strokes[index].push({x:x, y:y});
Question is about the onstart event handler for Element.drag in the newly announced Snap.svg.
The intention of the code below is to register event handlers for the start and stop of a drag (onstart/onstop) on an svg object.
var s = Snap(800,600);
var bigCircle = s.circle(300,150,100);
bigCircle.drag(null,
function(){
console.log("Move started");
},
function(){
console.log("Move stopped");
}
);
The console messages work fine on drag start and stop, but the null overrides the default onmove function - resulting in no actual drag happening. How do I pass something that says "I don't want to mess with the default onmove"?
(Note: I'd prefer to register an event handler by means of assignment, like the familiar onClick, but that's a different matter.)
Note added after few hours:
The Raphael.js documentation and examples provide some clues. At least now I know how to pass in a proper function for onmove that provides the default move behavior:
var s = Snap(800,600);
var bigCircle = s.circle(300,150,100);
start = function() {
this.ox = parseInt(this.attr("cx"));
this.oy = parseInt(this.attr("cy"));
console.log("Start move, ox=" + this.ox + ", oy=" + this.oy);
}
move = function(dx, dy) {
this.attr({"cx": this.ox + dx, "cy": this.oy + dy});
}
stop = function() {
this.ox = parseInt(this.attr("cx"));
this.oy = parseInt(this.attr("cy"));
console.log("Stop move, ox=" + this.ox + ", oy=" + this.oy);
}
bigCircle.drag(move, start, stop);
I'm not sure if I'm misunderstanding what you exactly want...don't you want to implement the drag ?
So for example...
var s = Snap(400,400);
var bigCircle = s.circle(150, 150, 100);
var moveFunc = function (dx, dy, posx, posy) {
this.attr( { cx: posx , cy: posy } ); // basic drag, you would want to adjust to take care of where you grab etc.
};
bigCircle.drag( moveFunc,
function(){
console.log("Move started");
},
function(){
console.log("Move stopped");
}
);
JSBin here http://jsbin.com/akoCAkA/1/edit?html,js,output
There is an example how to drag with SnapSVG here: http://svg.dabbles.info/snaptut-drag.html
var s = Snap("#svgout");
var rect = s.rect(20,20,40,40);
var circle = s.circle(60,150,50);
var move = function(dx,dy) {
this.attr({
transform: this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy]
});
}
var start = function() {
this.data('origTransform', this.transform().local );
}
var stop = function() {
console.log('finished dragging');
}
rect.drag(move, start, stop );
circle.drag(move, start, stop );
After struggling for some hours to do this with snap.js, I finally discovered svg.js and its draggable plugin, with which it is so much easier:
var draw = SVG('svg');
var circle = draw.circle(10).attr({cx:30,cy:30,fill:'#f06'});
circle.dragend = function(delta, event) {
alert(this.attr('cx'))
}
circle.draggable();
So, I switched to svg.js ...
The eve.on method wasn't working for me, so I did some poking around and managed to recreate the onmove function. The other two (onstart and onend) require no specific code to work apparently:
var S = Snap(300,300);
var bigCircle = S.circle(150, 150, 100);
bigCircle.drag(onDragMove, onDragStart, onDragEnd);
var ddx = 0;
var ddy = 0;
var dxDone = 0;
var dyDone = 0;
function onDragMove (dx, dy, posx, posy) {
dx = dx + dxDone; // dx and dy reset to 0 for some reason when this function begins
dy = dy + dyDone; // retain the last move's position as the starting point
this.attr( { transform: 't'+dx+','+dy } );
ddx = dx;
ddy = dy;
console.log('moving...');
};
function onDragStart(x,y,e) {
console.log('start!');
};
function onDragEnd(e) {
dxDone = ddx;
dyDone = ddy;
console.log('end!');
};
Please note however that this should only be used for one dragged object at a time. If you need a custom drag for another object, you'll have to rename the functions (ie onDragStart2) and the four variables declared outside of them (ie ddx2) after duplicating it.
Also, the 'transform' string format I passed (tx,y) came from what I found after doing console.log( this.attr('transform') ). I'm not familiar with matrix() just yet, so this way seemed easier.
Hope this helps!
I can't drag group elements with custom handlers, s.drag() makes it possible. So i searched further found its possible.
Documentation:
Additionaly following drag events are triggered: drag.start. on start, drag.end. on > end and drag.move. on every move. When element is dragged over another element > drag.over. fires as well.
Solution:
s.drag();
eve.on("snap.drag.start." + s.id, function () {
console.log('cool');
});
eve.on("snap.drag.move." + s.id, function () {
console.log('cooler');
});
eve.on("snap.drag.end." + s.id, function () {
console.log('way cool');
});
eve is not documented on snapsvg it is available on raphael. i don't know this is proper way or hack.
Try this
var paper = Snap("#main");
var object = paper.circle(300,150,100)
object .attr({
stroke: "#000",
strokeWidth: 10,
strokeLinecap:"round"
});
var move1 = function(dx,dy, posx, posy) {
this.transform(this.data('origTransform') + (this.data('origTransform') ? "T" : "t") + [dx, dy])
};
var start = function() {
this.data('origTransform', this.transform().local );
}
var stop = function() {
console.log('dragging done');
}
object.drag(move1, start, stop );
I'm writing a simple game in javascript and I'm wondering what the best way to handle collisions between the player and the world objects.
<script>
var isJumping = false;
var isFalling = false;
var w = 1;
var recwidth = 400;
var recheight = 400;
var xpos = 50;
var ypos = 279;
window.onload = function() {
var FPS = 30;
var ground = new myObject();
setInterval(function() {
clear();
draw();
ground.draw(0, 325);
ground.draw(125,325)
}, 1000/FPS);
};
function myObject(){
this.draw = function drawground(groundx, groundy){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
//context.fillRect(xpos,ypos,100,100);
var img=new Image()
img.src="ground.png"
img.onload = function() {
context.drawImage(img,groundx,groundy)}
}
};
function jump()
{
var t=.1;
isJumping=true;
var jumpint= setInterval(function() {
yup = 12*t-(5*t*t);
ypos= ypos - yup;
t = t + .1
if(yup < 0)
{
isJumping = false;
isFalling = true;
clearInterval(jumpint);
jumpint = 0;
fall();
return;
}
}, 20);
}
function fall()
{
t=.10
var fallint= setInterval(function() {
ydown = (5*t*t);
ypos= ypos + ydown;
t = t + .1
if(ypos > 275)
{
isFalling == false;
clearInterval(fallint);
fallint = 0;
return;
}
}, 20);
}
function changex(x){
xpos = xpos + (x);
//clear();
//draw();
}
function changey(y){
ypos = ypos + (y);
//clear();
//draw();
}
function draw(){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
var img=new Image()
img.src="character.png"
img.onload = function() {
context.drawImage(img,xpos,ypos)}
}
function clear(){
var canvas = document.getElementById('canvas')
var context = canvas.getContext('2d');
context.clearRect(0,0, canvas.width, canvas.height);
}
document.onkeydown = function(event) {
var keyCode;
if(event == null)
{
keyCode = window.event.keyCode;
}
else
{
keyCode = event.keyCode;
}
switch(keyCode)
{
// left
case 37:
//left
changex(-5);
break;
// up
case 38:
// action when pressing up key
jump();
break;
// right
case 39:
// action when pressing right key
changex(5);
break;
// down
case 40:
// action when pressing down key
changey(5);
break;
default:
break;
}
}
</script>
So, as you can see I'm creating two objects so far, and the player stops falling at any arbitrary point. I feel collisions at this stage wont be too difficult, but once I start adding more I feel it's going to get more difficult. I'm not going to be using the instance of the object with the same image for each instance of the object, so at some point I'm going to change the myobject function to be able to accept the image as a parameter, and then checking for collisions will be a bit more tricky. I also plan on making this into a side scroller, so once one end the map is hit it changes into the next area, which is going to cause performance issues. If I'm checking for collisions on every single object in the entire game every interval I imagine things are going to get slow. What is going to be the best way to limit the number of collisions checked? Obviously, if the object isn't on screen there is no need to check it, but is there a way to limit that. I'm thinking of making an array for every frame of the game, and filling that array with it's objects. Then, only check the array the of the frame the player is currently in. Is this feasible or still going to cause too many issues? Any help is greatly appreciated.
If you want pixel perfect collisions, I have some plain javascript code that worked for me with canvas2d rendering context.
function collide(sprite, sprite2, minOpacity=1) {
// Rectangular bounding box collision
if (sprite.x < sprite2.x + sprite2.width && sprite.x + sprite.width > sprite2.x && sprite.y < sprite2.y + sprite2.height && sprite.y + sprite.height > sprite2.y) {
// Finds the x and width of the overlapping area
var overlapX = (this.rect.x > other.rect.x) ? [this.rect.x, (other.rect.x + other.rect.width) - this.rect.x + 1] : [other.rect.x, (this.rect.x + this.rect.width) - other.rect.x + 1];
// Finds the y and height of the overlapping area
var overlapY = (this.rect.y + this.rect.height > other.rect.y + other.rect.height) ? [this.rect.y, (other.rect.y + other.rect.height) - this.rect.y + 1] : [other.rect.y, (this.rect.y + this.rect.height) - other.rect.y + 1];
// Creates a canvas to draw sprite.image to
var spriteImageCanvas = new OffscreenCanvas(overlapX[0] + overlapX[1], overlapY[0] + overlapY[1]);
var spriteImageCanvasContext = spriteImageCanvas.getContext("2d");
// Draws sprite.image to spriteImageCanvasContext
spriteImageCanvasContext.drawImage(this.image, sprite.x, sprite.y, sprite.width, sprite.height);
// Creates a canvas to draw sprite2.image to
var sprite2ImageCanvas = new OffscreenCanvas(overlapX[0] + overlapX[1], overlapY[0] + overlapY[1]);
var sprite2ImageCanvasContext = otherImageCanvas.getContext("2d");
// Draws sprite2.image to sprite2ImageCanvasContext
sprite2ImageCanvasContext.drawImage(sprite2.image, sprite2.x, sprite2.y, sprite2.width, sprite2.height);
// Loops through the x coordinates in the overlapping area
for (var x = overlapX[0]; x <= overlapX[0] + overlapX[1]; x++) {
// Loops through the y coordinates in the overlapping area
for (var y = overlapY[0]; y <= overlapY[0] + overlapY[1]; y++) {
if (/* Checks if the pixel at [x, y] in the sprite image has an opacity over minOpacity input */ thisImageCanvasContext.getImageData(x, y, 1, 1).data[3] >= minOpacity && /* Checks if the pixel at [x, y] in the sprite2 image has an opacity over minOpacity input */ otherImageCanvasContext.getImageData(x, y, 1, 1).data[3] >= minOpacity) {
return true;
};
};
};
};
}
Or if you just want rectangular collision, use the first if statement in the function.
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