I’m using svg.js to manipulate SVGs within the browser. Most of what I’m doing is relatively simple, but I’m having some difficulties with scaling/positioning a few objects.
I have an SVG that contains a “Pin” icon. You can click on the SVG to zoom it (which just resizes it to fill the browser viewport), in turn resizing all of its children, including the Pin. I need to scale this icon back down to its original size of 36px x 36px and reposition it so the bottom center of the Pin sits where it originally sat. I’ve got the resizing down, but the repositioning piece escapes me.
Some example states:
Scaled at 100% with Pin at base size of 36px x 36px.
Scaled up by 9.77241379 with pin scaled down to its base size of 36px x 36px. Using svg.js the scale() method scales at the center point of the pin, leaving it floating in space.
What I’m using to scale the Pin when the parent container is scaled up:
scaleHotspot(hotspot) {
const child = hotspot.first();
const bbox = child.bbox();
const rbox = child.rbox();
const scale = bbox.w / rbox.w;
hotspot.scale(scale);
}
Because the Pin is scaled using its center point it’s now sitting up higher than it’s supposed to be. I need to determine how much to move the Pin down to have the point of the Pin sitting in its original position.
I originally thought this worked but testing in various places yielded strange results.
const newY = -(bbox.height / 2 - (bbox.height - (1 / scale)));
Suggestions around getting the Pin positioned so that its bottom center point sits where the unscaled version was?
With .transform(), you can define a center around which to scale. Use .bbox() to find that point in current user space. Since these coordinates already include pre-existing transformations, set the relative flag to add the scaling on top:
scaleHotspot(hotspot) {
const bbox = hotspot.bbox();
const rbox = hotspot.rbox();
const scale = bbox.w / rbox.w;
const center = bbox.x + bbox.w / 2;
const bottom = bbox.y + bbox.h;
hotspot.transform({scale: scale, cx: center, cy: bottom}, true);
}
Related
I'm currently working on a mini map for a game in which keeps track of different items of importance on and off the screen. When I first created the mini map through a secondary camera rendered onto a texture and displayed on screen in a miniature display, it was rectangle shape. I was able to ensure when the item of importance left the view of the map, an arrow pointing to the target showed up and remained on the edge of the map. It was basically clamping the x & y positions of the arrow to half the camera view's width and length (with some suitable margin space).
Anyway. Now I am trying to make the mini map circular and while I have the proper render mask on to guarantee that shape of the mini map, I am having difficulties in clamping the arrows to the shape of the new mini-map. In the rectangular mini map, the arrows stayed in the corners while clamped, but obviously, circles don't have corners.
I am thinking clamping the arrow's x & y positions have to do with the radius of the circle (half of the height of the screen/minimap), but because I'm a little weak on the math side, I am kindly requesting some help. How would I clamp the arrows to the edge of a new circle shape?
The code I have now is as follows:
let {width: canvasWidth, height: canvasHeight} = cc.Canvas.instance.node, // 960, 640
targetScreenPoint = cc.Camera.main.getWorldToScreenPoint(this.targetNode.position)
// other code for rotation of arrow, etc...
// FIXME: clamp the the edge of the minimap mask which is circular
// This is the old clamping code for a rectangle shape.
let arrowPoint = targetScreenPoint;
arrowPoint.x = utils.clamp(arrowPoint.x, (-canvasWidth / 2) + this.arrowMargin,
(canvasWidth / 2) - this.arrowMargin);
arrowPoint.y = utils.clamp(arrowPoint.y, (-canvasHeight / 2) + this.arrowMargin,
(canvasHeight /2) - this.arrowMargin);
this.node.position = cc.v2(arrowPoint.x, arrowPoint.y);
I should probably also note that all mini-map symbols and arrows technically are on screen but only are displayed in on the secondary camera through a culling mask... you know, just in case it helps.
Just for anyone else looking to do the same, I basically normalized the direction from the target node that the arrow points at and multiplied it by the radius of the image mask (with appropriate margin space).
Since the player node and the centre of the mask is at origin, I just got the difference from the player. The (640/2) is the diameter, which of course, shouldn't be hardcoded, but meh for now. Thanks to those who commented and got me thinking in the right direction.
let direction = this.targetNode.position.sub(this.playerNode.position).normalize();
let arrowPos = direction.mul((640/2) - this.arrowMargin);
this.node.position = arrowPos;
I am new to css and learning different type of css styles. I want to learn how the effect used in official MongoDb website. The effect which tracks the mouse position and transforms the boxes. I know how to do the transform in css. But, how can it be done with the mouse position. Thanks for the help.
General overview of how to do it:
Register a mousemove-handler and track your mouse-screen location (see link)
translate mouse screenlocation, to mouse location relative to rectangle:
e.target in mousemove event gives you the rectangle (or some descendent which allows you to get to the rectangle.
given the target element get it's position (top + left using getBoundingClientRect) as well as width and height. These should be easy to lookup
Notice that the mouse at the center of the rectangle doesn't rotate the rectangle. Only when moving to the edges, the rotation starts to get going. This rotational rate-of-change seems to be linear. So:
determine the max rotation that seems nice to have in degrees. Simply test with different numbers in the chrome dev tools or similar: transform: rotateY(0.01deg) rotateX(0.01deg); Say you want to have a max rotation of 25 degrees.
say the rectangle is 100px in width. It's clear to see that each pixel movement from the center to the edge (50 px in total) adds 0.5 degree to the rotation due to the linear rate of change: 25 deg / 50px. So for example moving 20px to the left of the center translates to rotateY(10deg);. Moving 20px to the right results in the mirror rotation (rotateY(-10deg);). NOTE: the positive and negative may need to be flipped.
similarly, moving along the Y-axis changes the rotateX-property.
Once calculated, set the css-property and you're done
I believe this must be done with Javascript. The general idea is when the mouse enter/move on the element, you compare it's coordinate with the position and width/height of the element to decide the rotation values. When the mouse leave the element, you reset the values of the rotation back to normal.
You can get the coordinate of the mouse from event by using:
const mouseX = event.clientX; // Get the horizontal coordinate
const mouseY = event.clientY; // Get the vertical coordinate
And the position of the element:
const eleLoc = event.target.getBoundingClientRect();
From there you calculate the center and the width/height of the element:
const centerX = (eleLoc.right + eleLoc.left) / 2
const centerY = (eleLoc.bottom + eleLoc.top) / 2
const halfWidth = (eleLoc.right - eleLoc.left) / 2
const halfHeight = (eleLoc.bottom- eleLoc.top) / 2
Then you calculate the distance between the mouse and the center in percent. In the center, the distance is 0, at the border, it's 1 (or -1).
const percentX = (mouseX - centerX) / halfWidth
const percentY = (mouseY - centerY) / halfHeight
Now you only need to rotate X/Y based on the distance percent:
const degX = percentX * maxDegX
const defY = percentY * maxDegY
event.target.style.transform = `perspective(${yourPerspective}px) rotateX(${degX}deg) rotateY(${degY}deg)`
Remember to reset the transform when your mouse move out.
There are some libraries for this, ie: tilt.js
I would like to scale animate an SVG element to fit (preserving aspect ratio) a given area of the SVG.
I know about animate which performs relative animations
var s = Snap("#myelement");
s.animate({ 'transform' : 't100,100s5,5,165,175' },1000);
In principle it should be possible to achieve what I want by computing the parameters of the translation and the scaling.
The problem there is that I do not find accurate documentation of the parameters.
The arguments of t seem to be the relative x,y position and that of s the scale factors and the coordinates of the scale center.
However, how does the combined translation and scaling work? Does the relative translation position scale with the scaling, etc.?
In other words: How do I compute the relative translation and scaling parameters from the coordinates of the upper left and the lower right corner of the animation target element?
Alternatively: Is there a more suitable animate function in Snap?
You show a transform with several parts. The order of these parts is important. If you translate first and scale later, the resulting translation is scaled too. If you scale first and then translate the resulting translation is not affected by the scaling.
The animation you use in Snap.svg is the one I also use. (However I consider migrating to svg.js, since Snap.svg does not play well with Electron for example. I have to do some testing first, though)
Since Snap uses SVG syntax, to solve the problem one needs to understand SVG transformations (see here for an introduction: https://sarasoueidan.com/blog/svg-transformations/). In order to set up a combined SVG transformation it is important to understand that each transformation changes the coordinate system (rather than just the properties of an element in an absolute coordinate frame).
If you combine two transformations, scaling and translation, this means that the parameters of the second transformation depends on the first one.
To achieve a translation and scaling of an element to a given location and size in the coordinates of the ViewBox of an SVG, one can first perform the scaling to the new size choosing the center coordinates for the scaling as the center of the element. Then considerations for the following translations simplify as follows
function startAnimation() {
var svg = Snap("#baseSVG");
/* get the bounding box of the svg */
var bboxSvg = svg.getBBox();
var s = Snap("#element");
/* get the bounding box of the element */
var bbox = s.getBBox();
/* get the required scale factor (assuming that we want to fit the element inside the svg bounding box) */
var scale = Math.min(bboxSvg.width/bbox.width,bboxSvg.height/bbox.height)*0.8;
/* compute the translation needed to bring center of element to center of svg
the scale factor must be taken into account since the translation is based on the coordinate system obtained after the previous scaling */
var tx = (200-bbox.cx)/scale;
var ty = (200-bbox.cy)/scale;
/* perform the animation (make center of scaling the center of element) */
s.animate({ 'transform' : 's' + scale + ',' + scale + ',' + bbox.cx + ',' + bbox.cy + 't' + tx + ',' + ty },1000,mina.bounce);
s.drag();
}
This assumes that your SVG object has id baseSVG and the element you want to transform has id element. It is transformed such that it fits the SVG (adjust the factor 0.8 if you want it larger or smaller). If you know only the coordinates of the corners of the element you must first compute the center coordinates of the target (replace bbox.cx and bbox.cy) and the scale to apply this code snippet. This works in the obvious way in the coordinate frame of baseSVG.
I am using the ariutta svg-pan-zoom library(https://github.com/ariutta/svg-pan-zoom). (Also using with jquery.layout.js panes and jquery-ui.js)
I would like to save the values such as pan and zoom values of an svg-pan-zoom svg and use those values in to jump to the same location using a browser with a different sized window.
Currently I am using getPan() and getZoom() to save the values; then zoom(zoom) and pan(pan) in the other browser to zoom and pan to the same location. That does not work when the browser window size is different.
I found this article, but it does not address a window that is a different size:
Pan to specific X and Y coordinates and center image svg-pan-zoom
Pan and zoom are relative to current container size. So what you want is to compute the center point of visible SVG's part. Then in new window compute what should be the new pan to have the that SVG's point (that was the center in previous case).
As about the zoom it depends on how you want to make it work, but you could use the real zoom.
To get container sizes and real zoom use instance.getSizes().
So for example to compute x axis do:
var s = instance.getSizes()
var p = instance.getPan()
var relativeX = s.width / 2 / (p.x + s.viewBox.width * s.realZoom)
// After resize
var s = instance.getSizes()
var p = instance.getPan()
var x = (s.width / 2 / relativeX) - s.viewBox.width * s.realZoom
instance.pan({x: x, y: 0})
Do the same for Y axis
I have an <img> within a <div> which can be moved around using four directional buttons, for example:
The image is obviously larger than its container, hence the directional buttons to move it in different directions.
There is also a zoom control where you can zoom in and out. I set up the scaling method with ease, by just applying a zoom factor as a percentage to the base width and height:
scale: function(zoom)
{
image.width = baseWidth * zoom;
image.height = baseHeight * zoom;
}
// Zoom in 50%.
Scene.scale(1.5);
This is fine however the image scales from top-left, meaning that the image looks like it's getting sucked out towards the top left during a zoom in and spat back out when zooming out.
I'm trying to have the zoom effect apply from the centre of the container, like this:
But I'm finding it hard to get my head around the mathematics required to move the image after scaling applies to give this effect.
The closest I've gotten is to move the image based on the difference between the current zoom and the new zoom level, but it's still slightly off and gives a 'curved' effect when zooming.
Is there a common formula used to reposition an image so that it scales around a different origin (i.e. not top-left (0,0)).
This is what it looks like currently.
You have to take your original coordinates and calculate the center of your original image, that is x_center = x_orig + width_orig / 2; Then you can calculate the new x coordinate of your scaled image: x_new = x_center - width_new / 2. The same applies for y. Then move your scaled image to these new coordinates. If you do it after each time you scale the image, it will look as though it is scaled around its center.