MotionPathPlugin.getRelativePosition
MotionPathPlugin.getRelativePosition( fromElement:Element | window, toElement:Element | window, fromOrigin:Array | Object, toOrigin:Array | Object | String ) : Object
Gets the x and y distances between two elements regardless of nested transforms! feed two elements to this method and it'll return the gap between them as a point {x, y} according to the coordinate system of the fromElement's parent.
Parameters
fromElement: Element | window
The element from which the distance is measured (typically this is the element that will be moved to the
toElement)toElement: Element | window
The destination element. This should be an element reference (like myElem), not a selector string (like "#myElem").
fromOrigin: Array | Object
[optional] Determines the point on the
fromElementthat serves as the origin of the measurements. It can be either an Array with progress values along the x and y axis like[0.5, 0.5](center),[1, 0](top right corner), etc. OR a point Object with pixel-based local coordinates like{x: 100, y: 0}toOrigin: Array | Object | String
[optional] Determines the point on the
toElementto which to measure. It can be either an Array with progress values along the x and y axis like[0.5, 0.5](center),[1, 0](top right corner), etc. OR a point Object with pixel-based local coordinates like{x: 100, y: 0}OR for If the toElement is a <path> you can use"auto"to align with the beginning of the path itself rather than using the bounding box.
Returns : Object
An object with "x" and "y" properties describing the distance along each axis
Details
Gets the x and y distances between two elements regardless of nested transforms! feed two elements to this method and it'll return the gap between them as a point {x, y} according to the coordinate system of the fromElement's parent. By default, it will align their top left corners (bounding box), but you can define a different origin for each, like [0.5, 0.5] would be the center, [1, 1] would be the bottom right, etc.
Sample code
let inner = document.querySelector("#inner"),
dot = document.querySelector("#dot"),
delta = MotionPathPlugin.getRelativePosition(
dot,
inner,
[0.5, 0.5],
[0.5, 0.5]
);
gsap.to(dot, {
x: "+=" + delta.x,
y: "+=" + delta.y,
duration: 2,
ease: "power2.inOut",
});
Video
Demo 1
<h4>GSAP 3 getRelativePosition() Demo</h4><div id="outer">
<div id="middle">
<div id="inner"></div>
</div>
</div>
<div id="dot-container">
<div id="dot"></div>
</div>/* GSAP brand: fonts, type sizes, colour vars, buttons and form controls.
Element selectors only, so a demo's own class-based styles still win. */
/* Fonts */
@font-face {
font-display: swap;
font-family: Mori;
font-weight: 400;
src: url(/fonts/Mori/PPMori-Regular.woff) format("woff");
}
@font-face {
font-display: swap;
font-family: Mori;
font-weight: 600;
src: url(/fonts/Mori/PPMori-SemiBold.woff) format("woff");
}
@font-face {
font-display: swap;
font-family: "Fraktion Mono";
font-weight: 400;
src: url(/fonts/Fraktion/PPFraktionMono-Bold.woff) format("woff");
}
/* Colours */
:root {
--color-shockingly-green: #0ae448;
--color-just-black: #0e100f;
--color-surface-white: #fffce1;
--color-pink: #fec5fb;
--color-shockingly-pink: #f100cb;
--color-orangey: #ff8709;
--color-lilac: #9d95ff;
--color-lt-green: #abff84;
--color-blue: #00bae2;
--color-grey: #191919;
--color-surface75: #bbbaa6;
--color-surface50: #7c7c6f;
--color-surface25: #42433d;
--gradient-macha: linear-gradient(114.41deg, var(--color-shockingly-green) 20.74%, var(--color-lt-green) 65.5%);
--gradient-orange-crush: linear-gradient(111.45deg, var(--color-orangey) 19.42%, #f7bdf8 73.08%);
--gradient-lipstick: linear-gradient(165.72deg, #f7bdf8 21.15%, #cd237f 81.93%);
--gradient-purple-haze: linear-gradient(153.58deg, #f7bdf8 32.25%, #2f3cc0 92.68%);
--gradient-skyfall: linear-gradient(131.77deg, #0a157a 30.82%, #15bfe4 81.82%);
--gradient-emerald-city: linear-gradient(166.9deg, var(--color-shockingly-green) 53.19%, #0085d0 107.69%);
--gradient-summer-fair: linear-gradient(144.02deg, var(--color-blue) 4.56%, var(--color-pink) 72.98%);
/* Short names many demos use */
--dark: var(--color-just-black);
--light: var(--color-surface-white);
--mid: var(--color-surface50);
--grey: var(--color-grey);
--gray: var(--color-grey);
--green: var(--color-shockingly-green);
--green-light: var(--color-lt-green);
--blue: var(--color-blue);
--purple: var(--color-lilac);
--pink: var(--color-pink);
--orange: var(--color-orangey);
--red: #cd237f;
/* Form controls */
--field-bg: #1c1d1f;
--field-border: var(--color-surface25);
--field-radius: 8px;
--focus-ring: 0 0 0 3px rgba(10, 228, 72, 0.35);
accent-color: var(--color-shockingly-green);
color-scheme: dark;
}
/* Type */
* { box-sizing: border-box; }
body {
margin: 0;
background-color: var(--color-just-black);
color: var(--color-surface75);
font-family: Mori, system-ui, sans-serif;
font-size: clamp(1rem, 0.95rem + 0.25vw, 1.125rem);
line-height: 1.45;
-webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4 {
margin: 0.5em 0;
color: var(--color-surface-white);
font-weight: 400;
line-height: 1.2;
}
h1 { font-size: clamp(2rem, 1.6rem + 2vw, 2.875rem); }
h2 { font-size: clamp(1.625rem, 1.4rem + 1.2vw, 2.2rem); }
h3 { font-size: clamp(1.375rem, 1.25rem + 0.6vw, 1.75rem); }
h4 { font-size: clamp(1.125rem, 1rem + 0.5vw, 1.4rem); }
a { color: inherit; text-underline-offset: 0.15em; }
code, pre, kbd { font-family: "Fraktion Mono", ui-monospace, Menlo, monospace; }
/* Buttons */
button,
input[type="button"],
input[type="submit"],
input[type="reset"] {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 0.5em;
min-height: 2.5rem;
padding: 0 1.25rem;
border: 2px solid var(--color-surface75);
border-radius: 99px;
background: var(--color-just-black);
color: var(--color-surface75);
font: 600 0.9rem/1 Mori, system-ui, sans-serif;
cursor: pointer;
transition: border-color 0.2s, color 0.2s, background-color 0.2s;
}
button:hover,
input[type="button"]:hover,
input[type="submit"]:hover,
input[type="reset"]:hover {
border-color: var(--color-surface-white);
color: var(--color-surface-white);
}
button:disabled,
input:disabled { opacity: 0.4; cursor: not-allowed; }
/* Form controls */
label { color: var(--color-surface75); font-size: 0.9rem; }
input[type="text"],
input[type="number"],
input[type="email"],
input[type="search"],
input[type="url"],
input:not([type]),
select,
textarea {
min-height: 2.5rem;
padding: 0 0.75rem;
border: 1px solid var(--field-border);
border-radius: var(--field-radius);
background: var(--field-bg);
color: var(--color-surface-white);
font: 400 0.9rem/1.2 Mori, system-ui, sans-serif;
transition: border-color 0.2s, box-shadow 0.2s;
}
textarea { padding: 0.6rem 0.75rem; line-height: 1.45; }
input::placeholder,
textarea::placeholder { color: var(--color-surface50); }
input[type="checkbox"],
input[type="radio"] {
width: 1.1em;
height: 1.1em;
margin: 0 0.4em 0 0;
vertical-align: -0.15em;
}
input[type="range"] { width: min(100%, 18rem); }
button:focus-visible,
input:focus-visible,
select:focus-visible,
textarea:focus-visible {
outline: none;
border-color: var(--color-shockingly-green);
box-shadow: var(--focus-ring);
}
body {
padding: 5px 15px;
}
/* Nested boxes */
#outer {
width: 500px;
height: 350px;
border: 2px dashed var(--color-surface-white);
border-radius: 16px;
}
#middle {
width: 300px;
height: 200px;
border: 2px dashed var(--color-surface-white);
border-radius: 12px;
}
#inner {
width: 100px;
height: 60px;
border: 2px dashed var(--color-surface-white);
border-radius: 8px;
}
/* Dot and its container */
#dot-container {
position: absolute;
top: 0;
left: 0;
box-sizing: content-box; /* keeps the original 100x50 content size */
width: 100px;
height: 50px;
border: 2px dashed var(--color-shockingly-green);
border-radius: 12px;
background-color: rgba(10, 228, 72, 0.05);
}
#dot {
position: absolute;
top: 0;
left: 0;
width: 16px;
height: 16px;
border-radius: 50%;
background-color: var(--color-shockingly-green); /* was red */
box-shadow: 0 0 14px rgba(255, 135, 9, 0.55);
}
h4 {
text-align: center;
}// The red dot goes to the center of the light rectangle even though it's nested inside multiple elements with various transforms applied!
// First, let's add some wacky transforms to every element (nested!)
var tl = gsap.timeline({defaults: {duration:1, delay:0.5, ease: "power1.inOut"}})
.to("#outer", {xPercent: 50, yPercent: 20, rotation: -20, y: -80})
.to("#middle", {x: 100, y: 50, scale: 0.7, rotation: 60})
.to("#inner", {x: 140, y: 100, transformOrigin: "50% 50%", rotation: 40})
.to("#dot-container", {x:50, y:80, scale:0.5, rotation:-25})
.call(function() {
// RED DOT MOVEMENT:
var inner = document.querySelector("#inner"),
dot = document.querySelector("#dot"),
delta = MotionPathPlugin.getRelativePosition(dot, inner, [0.5, 0.5], [0.5, 0.5]);
gsap.to(dot, {
x: "+=" + delta.x,
y: "+=" + delta.y,
duration: 2,
delay: 1,
repeat: -1,
yoyo: true,
repeatDelay:1,
ease: "power2.inOut"
});
});Demo 2
You can also localize the pointer coordinates, like in the below demo (click anywhere)
<h1>getRelativePosition() dot to pointer</h1>
<p><strong>Click anywhere!</strong> The green dot that's inside the rotated/scaled container will move to where the pointer clicked by using the <a href="https://greensock.com/docs/v3/Plugins/MotionPathPlugin/static.getRelativePosition()">getRelativePosition()</a> method. You can also drag the container and it'll still work.</p><br>
<div id="dot-container">Dot container<div id="dot"></div></div>body {
padding: 10px 15px;
text-align: center;
overflow: hidden;
background-color: #111;
color: #aaa;
}
p {
font-weight: 300;
max-width: 900px;
display: inline-block;
text-align: left;
}
#dot-container {
border: 2px dashed var(--color-surface-white);
border-radius: 10px;
width: 200px;
height: 100px;
font-size: 28px;
line-height: 45px;
display: inline-block;
position: relative;
}
#dot,
#dot-container::after {
position: absolute;
top: 45px;
left: 88px;
border-radius: 50%;
width: 24px;
height: 24px;
z-index: 100;
background-color: var(--color-shockingly-green);
}
#dot-container::after {
content: '';
top: 45px;
left: 88px;
z-index: 0;
background-color: black;
}gsap.registerPlugin(Draggable, MotionPathPlugin);
let dot = document.querySelector("#dot"),
dotContainer = document.querySelector("#dot-container");
// apply some transforms to make it more impressive
gsap.to(dotContainer, {scale:0.6, rotation:25, y: 10, x: -10, duration:1.5, delay: 1});
document.addEventListener("click", function(e) {
var p = MotionPathPlugin.getRelativePosition(dot, window, [0.5, 0.5], {x: e.pageX, y: e.pageY});
gsap.to(dot, {
x: "+=" + p.x,
y: "+=" + p.y,
duration: 0.5
});
});
// make it draggable
Draggable.create(dotContainer, {bounds: window});getRelativePosition()` was added in GSAP 3.2.0
