I need to position an svg path element at the click position
My example code is as follows
const svg = document.querySelector('svg');
const viewBox = svg.getAttribute('viewBox').split(' ');
const offset = svg.getBoundingClientRect();
svg.addEventListener('click', (e) => {
const width = svg.clientWidth;
const ratio = viewBox[2] / width;
const x = (e.pageX - offset.x) * ratio ;
const y = (e.pageY - offset.y) * ratio;
const tp = document.createElementNS('http://www.w3.org/2000/svg', 'path');
tp.setAttribute('fill', 'F7931E');
tp.setAttribute('stroke', '#000');
tp.setAttribute('d', 'M37,17v15H14V17z M50,0H0v50h50z');
tp.setAttribute('transform', `scale(1) translate(${x}, ${y })`);
svg.appendChild(tp);
});
DEMO
As you can see there is a lot to take into account in order to possition the path where you click. Now, I would like to scale the path
tp.setAttribute('transform', `scale(.3) translate(${x}, ${y})`);
This, unfortunately, changes everything; DEMO with scale = .3
So the question is, how can I compute the correct transform values in this case ?
Probably a bit related, as you can see, if scaling is off, the path is placed such that the mouse pointer is at the top-left corner. I would like to have it at its center. Any guidinance would be appreciated!
One final note, I'm looking for a solution which is generic, meaning it works for any shape
Related
I want to crop image using fabricJs but I'm stuck on the calculus of the correct area to be cropped.
When I click on the crop button, it doesn't crop the image to the correct area.
The calculation is wrong.
Here's my crop code:
function centerXY (w, h) {
return {
x: canvas.width / 2 - w / 2,
y: canvas.height / 2 - h / 2
}
}
// zoneWidth: width of area where I want to cropped
// zoneHeight: height of area where I want to cropped
$('#crop').click(function () {
var zoom = canvas.getZoom()
const imgTop = canvasImage.top
const imgLeft = canvasImage.left
const {x, y} = centerXY(zoneWidth, zoneHeight)
let top = zoneHeight - imgTop - y
let left = zoneWidth - imgLeft - x
zoneWidth /= zoom
zoneHeight /= zoom
canvasImage.clipTo = ctx => {
ctx.rect(left, top, zoneWidth, zoneHeight)
};
canvas.renderAll()
})
This calculation is based on this post: Crop Functionality using FabricJs
Here's the demo.
Note: You can use the input type range to zoom out and see where the image is cropped.
I solved my problem.
Here's the solution:
$('#crop').click(function () {
var zoom = canvas.getZoom()
let top = zoneY // Top position of area
let left = zoneX// Left position of area
zoneWidth /= zoom
zoneHeight /= zoom
canvas.clipPath = shapeRect; // fabric.Rect()
canvas.renderAll()
})
Instead of cropping the image, I cropped the canvas itself.
And Using the position of the area to crop
Here the final demo: https://jsfiddle.net/58nvte7h/43/
I would like to be able to click on an object, and have it zoomed to its boundingbox in the canvas viewport. How do I accomplish that? See http://jsfiddle.net/tinodb/qv989nzs/8/ for what I would like to get working.
Fabricjs' canvas has the zoomToPoint method (about which the docs say: Sets zoom level of this canvas instance, zoom centered around point), but that does not center to the given point, but it does work for zooming with scrolling. See http://jsfiddle.net/qv989nzs/
I tried several other approaches, like using canvas.setViewportTransform:
// centers a circle positioned at (200, 150)??
canvas.setViewportTransform([2, 0, 0, 2, -250, -150])
But I can't find the relation between the last two parameters to setViewportTransform and the position of the object.
(Btw, another problem is with the first example fiddle, that the zooming only works on the first click. Why is that?)
I found a way to do this, which is composed of:
canvas.setZoom(1) // reset zoom so pan actions work as expected
vpw = canvas.width / zoom
vph = canvas.height / zoom
x = (object.left - vpw / 2) // x is the location where the top left of the viewport should be
y = (object.top - vph / 2) // y idem
canvas.absolutePan({x:x, y:y})
canvas.setZoom(zoom)
See http://jsfiddle.net/tinodb/4Le8n5xd/ for a working example.
I was unable to get it to work with zoomToPoint and setViewportTransform (the latter of which does strange things, see for example http://jsfiddle.net/qv989nzs/9/ and click the blue circle; it is supposed to put the top left viewport at (25, 25), but it does not)
Here's an example of how to do it with setViewportTransform:
// first set the zoom, x, and y coordinates
var zoomLevel = 2;
var objectLeft = 250;
var objectTop = 150;
// then calculate the offset based on canvas size
var newLeft = (-objectLeft * zoomLevel) + canvas.width / 2;
var newTop = (-objectTop * zoomLevel) + canvas.height / 2;
// update the canvas viewport
canvas.setViewportTransform([zoomLevel, 0, 0, zoomLevel, newLeft, newTop]);
I'm making a game in Phaser using some large images that I want to scale down in the actual game:
create() {
//Create the sprite group and scale it down to 30%
this.pieces = this.add.group(undefined, "pieces", true);
this.pieces.scale.x = 0.3;
this.pieces.scale.y = 0.3;
//Add the players to the middle of the stage and add them to the 'pieces' group
var middle = new Phaser.Point( game.stage.width/2, game.stage.height/2);
var player_two = this.add.sprite(middle.x - 50, middle.y, 'image1', undefined, this.pieces);
var player_one = this.add.sprite(middle.x, middle.y-50, 'image2', undefined, this.pieces);
}
However because the sprites are scaled in size, their starting location is also scaled, so instead appearing in the middle of the stage, they appear only 30% of the distance to the middle.
How do I scale the sprite image without it affecting their location?
(The code is incidentally in Typescript but I think this particular sample is also javascript so that's probably irrelevant)
Set Sprite.anchor to 0.5 so the physics body is centered on the Sprite, scale the sprite image without it affecting their location.
this.image.anchor.setTo(0.5, 0.5);
Doc Phaser.Image
Anchor example
You can scale your sprite like so:
var scaleX = 2;
var scaleY = 2;
sprite.scale.set(scaleX, scaleY);
then calculate position of sprite:
var positionX = 100;
var positionY = 100;
sprite.x = positionX / scaleX;
sprite.y = positionY / scaleY;
Now your sprite is at position (100, 100).
The problem is that sprite.x got multiplied by with scaleX.
Regarding Phaser, I'd like to add that in the specific case of weapon.bullets or other groups you create yourself you're going to have to do it this way instead:
weapon.bullets.setAll('scale.x', 0.5);
weapon.bullets.setAll('scale.y', 0.5);
I got stuck on this and ended up in this thread, which is closed but in my case just not what I needed. Others will hopefully have some use out of this :)
I am trying to scale/move an SVG path created with the Raphael api. I want the path to fit neatly within a container, no matter the size of the container. I have searched the reference, the web and I'm still struggling to get this to work.
If anyone can tell me why this isn't working, I would be very happy.
This fiddle shows you what I'm doing: http://jsfiddle.net/tolund/3XPxL/5/
JavaScript:
var draw = function(size) {
var $container = $("#container").empty();
$container.css("height",size+"px").css("width",size+"px");
var paper = Raphael("container");
var pathStr = "M11.166,23.963L22.359,17.5c1.43-0.824,1.43-2.175,"+
"0-3L11.166,8.037c-1.429-0.826-2.598-0.15-2.598,"+
"1.5v12.926C8.568,24.113,9.737,24.789,11.166,23.963z";
// set the viewbox to same size as container
paper.setViewBox(0, 0, $container.width(), $container.height(), true);
// create the path
var path = paper.path(pathStr)
.attr({ fill: "#000", "stroke-width": 0, "stroke-linejoin": "round", opacity: 1 });
// get the path outline box (may be different size than view box.
var box = path.getBBox();
// move the path as much to the top/left as possible within container
path.transform("t" + 0 + "," + 0);
// get the width/height based on path box relative to paper (same size as container)
var w = (paper.width) / box.width;
var h = (paper.height) / box.height;
// scale the path to the container (use "..." to compound transforms)
path.transform('...S' + w + ',' + h + ',0,0');
}
$(function() {
var currentSize = 30;
draw(currentSize);
$("#smaller").click(function(){
currentSize = currentSize < 10 ? currentSize : currentSize * 0.5;
draw(currentSize);
});
$("#bigger").click(function(){
currentSize = 300 < currentSize ? currentSize : currentSize * 2;
draw(currentSize);
});
});
HTML:
<button id="smaller">-</button>
<button id="bigger">+</button>
<div id="container" style="border: 1px #ddd solid; margin: 30px">
</div>
Thanks,
Torgeir.
I think your problem is a fundamental misunderstanding of what the viewbox is useful for. In your code, you're attempting to set the viewbox of the svg element so that it matches the coordinate space of the screen, and then transform the path to match that coordinate space. There's no technical reason you can't do this, but it does effectively take the "Scalable" out of "Scalable Vector Graphics." The entire point of the viewbox is to make the translation between the vector coordinate space and the screen relative.
The best way to solve your problem, then, is not to transform the path to match the SVG element, but to use the viewbox to let SVG's intrinsic scalability do this for you.
First things first: create your path so we have an object to work with. We don't care what the viewbox is at this point.
var pathStr = "..."; // The content of the path and its coordinates are completely immaterial
var path = paper.path(pathStr)
.attr({ fill: "#000", "stroke-width": 0, "stroke-linejoin": "round", opacity: 1 });
Now all we need to do is use the viewbox to "focus" the SVG on the coordinate space we're interested in.
var box = path.getBBox();
var margin = Math.max( box.width, box.height ) * 0.1; // because white space always looks nice ;-)
paper.setViewBox(box.x - margin, box.y - margin, box.width + margin * 2, box.height + margin * 2);
And that's it. The SVG (regardless of its size) will translate from the internal coordinates specified in the viewbox to its physical coordinates on screen.
Here's a fork of your fiddle as proof-of-concept.
im looking for an implementation of a card layout:
given a fixed width canvas, an arbitrary number of rectangles will be laid out, with some margins initially. fixed Y coordinate.
if the number of rectangles doesnt fit the canvas width, each rectangle will be partly overlayed by the next one. the last reactangle showing its whole width
wanted to have the layout auto adjust as I add a new rectangle
if there is such thing exist already or similar, pls share. was hoping i dont have to write it.
Like #puggsoy, I can't think of a library that does this, but here's a simple function that should do what you need.
function updateLayout(canvasWidth:Number, rects:Array, rectWidth:Number, margin:Number):void
{
var totalWidth:Number = rects.length * rectWidth + ((rects.length-1)*margin);
var offset:Number = rectWidth + margin;
if(totalWidth > canvasWidth)
{
offset = (canvasWidth - rectWidth) / (rects.length-1);
}
var currX:Number = 0;
for each(var rect:DisplayObject in rects)
{
rect.x = currX;
currX += offset;
}
}
This assumes that the DisplayObjects in the rects array are ordered by their childIndex.