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
Related
Given initial coordinates (the left and top css properties) of an absolutely positioned element on screen, and an angle of movement, how can I find out the values I should feed to transform: translate to animate the element? (The element would eventually fall off-screen, so one of the final coords will be 0 or window.innerWidth or window.innerHeight.)
For example, starting from left: 0 and top: 0, and given an angle of 90°, the destination point would be at left equal to window.innerWidth and top equal to 0. If the angle is 135°, the element would end up in the bottom right corner, etc. How can I calculate this for any angle?
(transform: translate takes deltas as parameters, not absolute positions; either is fine)
It is actually fairly simple trig. Based on the example you have given:
Your known quantities are initial coords { x: 0, y: 0 } and { angle: 90 /* in degrees*/ }
Find x-distance between initial x-coord and { window.innerWidth if angle is between 90-270 or 0 if angle between 0-90 & 270-360 }
Find the cosine of angle and multiply it with x-distance
Find y-distance between initial y-coord and { window.innerHeight if angle between 180-360 or 0 if angle between 0-180 }
Find the sine of angle and multiply it with y-distance
Add the initial x-coord with result of cosine * x-distance. This gives new x-coordinate
Add the initial y-coord with result of sine * y-distance. This gives new y-coordinate
Add some buffer to xnew and ynew so they fall off screen
For moving the element, you no longer require transform. You can simply add a transition CSS property and define new coords with new class added to that element. JSFiddle Demo
HTH
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);
}
I have an <img> that is zoomed upon mousewheel scrolling, by adjusting transform: scale(). I want the zooming to be like in Google Maps, where you zoom to where the mouse cursor is, not to the center of the image. I'd like to not use canvas though, just for the learning experience (that's also why the other questions I found did not really help).
I set up a JSFiddle to demonstrate the problem. My thought process was as follows: when zooming in by 10%, the image expands in all directions, from the center of the image, by 10%. That means that e.g., the left and right edge will travel 5% of the original width in each direction. I therefore tried to solve the problem like so:
Calculate mouse offset from image center
Calculate new image offset (top and left) by multiplying mouse offset with zoom factor and divide by two
Apply offset and watch it all blow up it my face with the power of a million burning suns
It seems that I just can't find a formula or algorithm that fits.
Eventually I figured it out myself, although only by looking at existing solutions. Here is the JSFiddle that contains only the essentials.
The idea is to first set transform-origin: 0 0. This makes sure that, upon zooming, the image expands down and right, instead of distributing the increase in width over all four sides. Note that it does not reposition the image, it just changes the origin for all transformations.
Additionally, this JSFiddle assumes that the top and left margins of the image are aligned with the top and left margins of the container element. If the image should be repositioned before zooming occurs, this should be done through transform: translate() and the translateX and translateY values need to be updated accordingly.
The heart of the logic is this:
// Track the percentage change between the old
// and the new scale of the image
const ratio = 1 - nextScale / currentScale
// get the current mouse offset
const {
clientX,
clientY
} = event
// The += here is extremely important!
// The new 2D translation values are derived from the difference
// between mouse cursor position and current (!) 2D translation.
// So where is the mouse cursor relative to the translated image
// This difference is then adjusted by the % change of the scaling
translateX += (clientX - translateX) * ratio
translateY += (clientY - translateY) * ratio
/*
This would work for the first wheel scroll. But afterwards, the
image will not be translated enough to offset the zooming because
we're not taking into account the existing translation
translateX += (clientX - translateX) * ratio
translateY += (clientY - translateY) * ratio
*/
So to summarize the required steps:
Calculate the next scale
Calculate the current mouse offset relative to the translated image
Adjust the mouse offset for the change in scaling, e.g., const percentChange = 1 - nextScale / currentScale
Add the adjusted mouse offset to the existing values for translate()
Apply the transformation (scaling and the translation)
The linked JSFiddle also includes Lodash and transition: transform 330ms ease-in-out; to make the scrolling a little smoother and not affect browser performance too much.
You could use transform-origin : <position of your mouse pointer> :
transform-origin : 0% 0% points on the top left corner.
transform-origin : 100% 100% points on the bottom right corner.
Here's an example I made : https://jsfiddle.net/zez538L8/4/
The javaScript :
var currentzoom = 1;
function zoom(delta, e) {
var img = document.getElementById("test");
var width = img.offsetWidth; //calculating the size of the img (in px)
var height = img.offsetHeight;
var x = event.offsetX; //calculating the position of the mouse pointer on the picture (in px)
var y = event.offsetY;
var xpercent = x*100/width; //calculating the position of the mouse pointer on the picture (in %)
var ypercent = y*100/height;
img.style.transform = "scale("+currentzoom+")"; //scaling the picture
img.style.transformOrigin = xpercent + "% "+ ypercent +"%"; //transform-origin
currentzoom += delta;
}
For example it may be used in the application of manually adjusting the hands of the clock. I guess it probably involves translating the needle (to make the end point of the needle the centre of rotation) then rotating it, then translating the needle again.
But since the needle listens to the mouse event all the time, the 1st mouse event will be captured. The result is that the needle ends up being translated and not rotated at all. Mouse event is impossible to debug too...
Any idea or code snippets that I can refer to? Using Javascript or CSS to rotate both fine.
In your example, you will want to calculate the angle between the centre of the clock face (black dot), and the current mouse position (red dot), relative to the Y axis (cardinal north if you imagine a compass).
If I remember my trig correctly, you can calculate this by using the following:
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI;
// alter the angle to be relative to Y axis instead of X
angle += 90;
if(angle < 0) { angle = 360 + angle; }
In the formula, x and y are the coordinates of the two points, one of which you will know (it is the centre of the clock face), and the other you can get from the mouse move event.
Once you have the angle, you can simply translate to the the centre of the circle, rotate the canvas by the calculated amount, and draw the hand.
Update: I created a jsfiddle to illustrate the angle calculation:
http://jsfiddle.net/DAEpy/1/
I have a sprite animation, a small cannon rendered using a 3D app. I have exactly 360 frames for a 360 degree turn. Each image has a 100x100 pixel size.
So basically what I am trying todo is when I click anywhere in the page, the barrel of the cannon needs to rotate to point at the mouse cursor, sound simple maybe but I can't really get it to work very well, perhaps cause my math skills is lacking :P
What I currently have is something like this
/* This is my div with the cannon background image (360 images stitched into one) each "image area" is 100x100px */
obj.cannon = $('#cannon');
/* Get the X/Y of the cannon loc in the dom */
var cannonX = $(obj.cannon).offset().left;
var cannonY = $(obj.cannon).offset().top;
/* Get radians using atan2 */
var radians = Math.atan2(e.pageY-cannonY, e.pageX-cannonX);
/* Convert to degrees */
var degrees = radians * (180/Math.PI);
And this is where I am, I mean since the image width is 100px and I need to move the background-position by 100px to move the cannon one degree right, because 360 images * 100px = 36000px in total. So the stitched sprite is like 36000px wide.
So
Insert weird calculation here based on the current backgroundPosition of the image-sprite and apply new backgroundPosition based on where you click with the mouse cursor, then use some sort of setTimeout(animateIt, speed); to "animate" the background position to the new position.
function animateIt(){
if(newpos!=targetpos) { //Use < > here if we need to add or remove
newpos+=100; //Until we arrive at the new backgroundPosition
$(obj.cannon).css({backgroundPosition: newpos+'px' });
setTimeout(animateIt, speed);
}
}
Am I at all on the right track here, am I thinking correctly about this? I feel stupid, this should be a simple thing but right now I am having a brain meltdown I think =P. My problem is I don't know how to properly arrive at the "new target backgroundposition" and then animate it ++ or -- based on the current background position :/
Well, here is a simplified working example with 10 images.
I'll post the code and jsFiddle now, and I might come back later to cover it in depth. But basically you just order your images correctly, and then you pick the segment by using (Segments - Math.floor(degree / (360 / segments))). You may have to adjust your 0 degree. For example, I made my 0 equal to what would normal by 90.
Pay attention to the fact that the screen coordinates, x and y, increase right and down. This makes the degrees of atan work clockwise instead of the usual counter clockwise in coordinate systems where x and y increase right and up.
I added in some text output that shows the degrees and image segment being shown.
jQuery handles normalizing the x and y position nicely. Just take care that your CSS setup is cross browser.
Working jsFiddle example
Here's our image:
Here's our HTML:
<div id="main"><div id="img"></div></div>
<div id="info">
<span></span><br/>
<span></span>
</div>
CSS:
div#main {
width:500px;
height:500px;
border:2px #000 solid; }
div#img {
width:94px;
height:119px;
overflow:hidden;
top:50%;
left:50%;
margin-left:-45px;
margin-top:-60px;
position:relative;
background-image:url('http://imgur.com/3UPki.png');
background-position:0;}
div#info {
position: absolute;
bottom:0;
left:0; }
Javascript / jQuery:
$(function() {
var position = $("div#img").position(),
mouseX, mouseY, imgX, imgY, degree;
imgX = position.left;
imgY = position.top;
$("#main").mousemove(function(e) {
// degree is arctan y over x (soh,cah,toa)
degree = Math.atan2((e.pageY - imgY),(e.pageX - imgX))*(180 / Math.PI);
degree = (degree - 90) % 360;
// jQuery normalizes pageX and pageY
// transfrom from -180 to 180 ==> 0 to 360
if (degree < 0) degree = 180 + (180 - (Math.abs(degree)));
rotate(degree);
$("span:first").html("Segment: " + (9 - Math.floor(degree / 36)));
$("span:last").html("Degree: " + Math.floor(degree));
});
function rotate(degree) {
var off = 9 - Math.floor(degree / 36);
$("div#img").css("background-position",-off*94);
}
});
Working jsFiddle example
Keep in mind that the degrees you get from atan will start pointing right for zero degrees and go clockwise from there (-90 is up, 90 is down).
Each position of your image should correspond to a specific angle. Once you have the degrees measured (it looks like you have that part right), use some type of mapping to translate your degrees to the proper image offset. I don't know what your image looks like so I can't help with that. Assuming your image starts pointing right, and goes around clockwise from there, the degrees will correspond directly the the offset for the right image. (I suggest you arrange your frames like this for ease...)