Hello my dear fellows,
I've been trying to recreate the effect: image scales up as the mouse get closer to the center of the image found on https://www.davidwilliambaum.com/
I have been very unsuccessfull so far, as I am not sure how to approach the problem.
I started a codepen with some ideas : https://codepen.io/dindon-studio/pen/RwLwRKM
As you can see I first get the center coordinate of the image, and then i try some dirty formula to scales it up with the mouse distance.
But it is very buggy and not convincing at all.
Does anyone got a better approach?
Deep thanks for you help!
var mX, mY, distance, element
element = $('.project')
function calculateDistance(elem, mouseX, mouseY) {
return Math.floor(Math.sqrt(Math.pow(mouseX - (elem.offset().left+(elem.width()/2)), 2) + Math.pow(mouseY - (elem.offset().top+(elem.height()/2)), 2))); }
$(document).mousemove(function(e) {
mX = e.pageX;
mY = e.pageY;
distance = calculateDistance(element, mX, mY);
if (distance< 500 && distance >50){
var scaling = 1 + (1/distance) *100
gsap.to(".project", {duration: 0.01, scale: scaling,ease: "power2.in",});
}
});
I build off from your codepen and made some adjustments: https://codepen.io/Mookiie/pen/qBPBmNe
The higher the scalingFactor the closer the mouse needs to be for a size change.
function calculateCenter(image) {
var rect1 = image.getBoundingClientRect();
var x = rect1.left + rect1.width * 0.5;
var y = rect1.top + rect1.height * 0.5;
return { x: x, y: y }
}
function getDistance(x1, y1, x2, y2){
let y = x2 - x1;
let x = y2 - y1;
return Math.sqrt(x * x + y * y);
}
function distanceFromCenter(image, mouseX, mouseY) {
var imageCenter = calculateCenter(image);
return getDistance(imageCenter.x, imageCenter.y, mouseX, mouseY)
}
function adjustImage(image, mX, mY) {
var distance = distanceFromCenter(image, mX, mY);
const baseScale = 1
const maxScaling = 1.5;
const scalingFactor = 1;
const adjustedScaling = maxScaling - ((distance / 1000) * scalingFactor)
const scaling = adjustedScaling >= baseScale ? adjustedScaling : baseScale
gsap.to(image, {duration: 0.01, scale: scaling, ease: "power2.in",});
}
$(document).mousemove(function(e) {
const mX = e.pageX;
const mY = e.pageY;
const images = $("img")
images.each(function() {
adjustImage(this, mX, mY)
})
});
I have a parent div that displays a graph that can be rotated.
I want to place dots at mouse clicks. This is my place function:
module.exports.place = function (event, id) {
let div = document.createElement("div");
div.className = "container-fluid";
div.id = id;
let avakio_wrapper = document.getElementById("avakio");
avakio_wrapper.appendChild(div);
let parentPosition = getPosition(event.currentTarget);
xPosition = event.clientX - parentPosition.x - div.clientWidth / 2;
yPosition = event.clientY - parentPosition.y - div.clientHeight / 2;
div.style.left = xPosition + "px";
div.style.top = yPosition + "px";
}
When the graph isn't rotated it works as expected, though when i rotate the graph it places the dot first under mouse click and then it applies the rotation transform and moves it. I have screenshots showing what i describe. The red dot is showing the click position. I appreciate any help.
UPDATE!!!!
Here is an implementation of my code:
// Calculate clicking pos based of top-left corner of div
xPosition = event.clientX - parentPosition.x - dot.clientWidth / 2;
yPosition = event.clientY - parentPosition.y - dot.clientHeight / 2;
let centerx = parentDiv.clientWidth / 2;
let centery = parentDiv.clientHeight / 2;
// Find Rotation angle
let rot = parentDiv.style.getPropertyValue("transform");
if (rot != "") {
rot = rot.split("(")[1].split("deg")[0];
} else {
rot = 0;
}
// Perform Invert Rotation
xPosition = centerx - xPosition;
yPosition = centery - yPosition;
let xRot =
xPosition * Math.cos(rot * (Math.PI / 180)) +
yPosition * Math.sin(rot * (Math.PI / 180));
let yRot =
-xPosition * Math.sin(rot * (Math.PI / 180)) +
yPosition * Math.cos(rot * (Math.PI / 180));
// Calculate again based top-left corner
dot.style.left = centerx - xRot + "px";
dot.style.top = centery - yRot + "px";
However i run on some issues. When the parent div is rotated the dots are placed some pixels off based on the rotation angle. I have some screenshots of different rotation angles. I' ve done the maths on paper for many days but still can't figure out what causing it.
Here an application of the math.
They still move a bit (I think that is because of calculus imprecision). I'm kinda stuck here, maybe some will be able to fix it.
function rotate() {
let parent = document.getElementById("parent")
let rot = document.getElementById("value").value
let transform = parent.style.transform
transform = "rotate("+ (Number(transform.substr(7,transform.length-11)) + Number(rot)) +"deg)"
parent.style.transform = transform
let dots = document.getElementsByClassName("dot")
for (i = 0; i < dots.length; i++)
dotpos(parent, dots[i],rot)
}
function dotpos(parent,dot,rot) {
rot = rot * (Math.PI/180) //convert to radian
let xCenter = parent.offsetWidth /2
let yCenter = parent.offsetHeight /2
let x = xCenter - Number(dot.style.left.substring(0, dot.style.left.length - 2))
let y = yCenter - Number(dot.style.top.substring(0, dot.style.top.length - 2))
let xRot = x*Math.cos(rot)+y*Math.sin(rot)
let yRot = -x*Math.sin(rot)+y*Math.cos(rot)
dot.style.left = xCenter -xRot + "px"
dot.style.top = yCenter -yRot + "px"
}
#parent {
height: 100px;
width : 100px;
background-color: grey;
}
.dot {
height: 5px;
width : 5px;
border-radius: 5px;
background-color: red;
}
<div id="parent" style = "transform:rotate(0deg)">
<div class ="dot" style="position:relative;left:15px;top:30px;"></div>
<div class ="dot" style="position:relative;left:60px;top:50px;"></div>
</div>
<form>
<input type="text" id="value"><br>
</form>
<button onclick="rotate()">rotate</button>
i've a canvas dom element inside a div #content with transform rotateX(23deg) and #view with perspective 990px
<div id="view">
<div id="content">
<canvas></canvas>
</div>
</div>
if i draw a point (300,300) inside canvas, the projected coordinates are different (350, 250).
The real problem is when an object drawn in a canvas is interactive (click o drag and drop), the hit area is translated.
Which equation i've to use? Some kind of matrix?
Thanks for your support.
This is something I am dealing with now. Lets start out with something simple. Let's say your canvas is right up against the top left corner. If you click the mouse and make an arc on that spot it will be good.
canvasDOMObject.onmouseclick = (e) => {
const x = e.clientX;
const y = e.clientY;
}
If your canvas origin is not at client origin you would need to do something like this:
const rect = canvasDOMObject.getBoundingRect();
const x = e.clientX - rect.x;
const y = e.clientY - rect.y;
If you apply some pan, adding pan, when drawing stuff you need to un-pan it, pre-subtract the pan, when capturing the mouse point:
const panX = 30;
const panY = 40;
const rect = canvasDOMObject.getBoundingRect();
const x = e.clientX - rect.x - panX;
const y = e.clientY - rect.y - panY;
...
ctx.save();
ctx.translate(panX, panY);
ctx.beginPath();
ctx.strokeArc(x, y);
ctx.restore();
If you apply, for instance, a scale when you draw it, you would need to un-scale it when capturing the mouse point:
const panX = 30;
const panY = 40;
const scale = 1.5;
const rect = canvasDOMObject.getBoundingRect();
const x = (e.clientX - rect.x - panX) / scale;
const y = (e.clientY - rect.y - panY) / scale;
...
ctx.save();
ctx.translate(panX, panY);
ctx.scale(scale);
ctx.beginPath();
ctx.strokeArc(x, y);
ctx.restore();
The rotation I have not figured out yet but I'm getting there.
Alternative solution.
One way to solve the problem is to trace the ray from the mouse into the page and finding the point on the canvas where that ray intercepts.
You will need to transform the x and y axis of the canvas to match its transform. You will also have to project the ray from the desired point to the perspective point. (defined by x,y,z where z is perspective CSS value)
Note: I could not find much info about CSS perspective math and how it is implemented so it is just guess work from me.
There is a lot of math involved and i had to build a quick 3dpoint object to manage it all. I will warn you that it is not well designed (I dont have the time to inline it where needed) and will incur a heavy GC toll. You should rewrite the ray intercept and remove all the point clone calls and reuse points rather than create new ones each time you need them.
There are a few short cuts. The ray / face intercept assumes that the 3 points defining the face are the actual x and y axis but it does not check that this is so. If you have the wrong axis you will not get the correct pixel coordinate. Also the returned coordinate is relative to the point face.p1 (0,0) and is in the range 0-1 where 0 <= x <= 1 and 0 <= y <= 1 are points on the canvas.
Make sure the canvas resolution matches the display size. If not you will need to scale the axis and the results to fit.
DEMO
The demo project a set of points creating a cross through the center of the canvas. You will notice the radius of the projected circle will change depending on distance from the camera.
Note code is in ES6 and requires Babel to run on legacy browsers.
var divCont = document.createElement("div");
var canvas = document.createElement("canvas");
canvas.width = 400;
canvas.height = 400;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center
var ch = h / 2;
var ctx = canvas.getContext("2d");
// perspectiveOrigin
var px = cw; // canvas center
var py = 50; //
// perspective
var pd = 700;
var mat;
divCont.style.perspectiveOrigin = px + "px "+py+"px";
divCont.style.perspective = pd + "px";
divCont.style.transformStyle = "preserve-3d";
divCont.style.margin = "10px";
divCont.style.border = "1px black solid";
divCont.style.width = (canvas.width+8) + "px";
divCont.style.height = (canvas.height+8) + "px";
divCont.appendChild(canvas);
document.body.appendChild(divCont);
function getMatrix(){ // get canvas matrix
if(mat === undefined){
mat = new DOMMatrix().setMatrixValue(canvas.style.transform);
}else{
mat.setMatrixValue(canvas.style.transform);
}
}
function getPoint(x,y){ // get point on canvas
var ww = canvas.width;
var hh = canvas.height;
var face = createFace(
createPoint(mat.transformPoint(new DOMPoint(-ww / 2, -hh / 2))),
createPoint(mat.transformPoint(new DOMPoint(ww / 2, -hh / 2))),
createPoint(mat.transformPoint(new DOMPoint(-ww / 2, hh / 2)))
);
var ray = createRay(
createPoint(x - ww / 2, y - hh / 2, 0),
createPoint(px - ww / 2, py - hh / 2, pd)
);
return intersectCoord3DRayFace(ray, face);
}
// draw point projected onto the canvas
function drawPoint(x,y){
var p = getPoint(x,y);
if(p !== undefined){
p.x *= canvas.width;
p.y *= canvas.height;
ctx.beginPath();
ctx.arc(p.x,p.y,8,0,Math.PI * 2);
ctx.fill();
}
}
// main update function
function update(timer){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.fillStyle = "green";
ctx.fillRect(0,0,w,h);
ctx.lineWidth = 10;
ctx.strokeRect(0,0,w,h);
canvas.style.transform = "rotateX("+timer/100+"deg)" + " rotateY("+timer/50+"deg)";
getMatrix();
ctx.fillStyle = "gold";
drawPoint(cw,ch);
for(var i = -200; i <= 200; i += 40){
drawPoint(cw + i,ch);
drawPoint(cw ,ch + i);
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);
// Math functions to find x,y pos on plain.
// Warning this code is not built for SPEED and will incure a lot of GC hits
const small = 1e-6;
var pointFunctions = {
add(p){
this.x += p.x;
this.y += p.y;
this.z += p.z;
return this;
},
sub(p){
this.x -= p.x;
this.y -= p.y;
this.z -= p.z;
return this;
},
mul(mag){
this.x *= mag;
this.y *= mag;
this.z *= mag;
return this;
},
mag(){ // get length
return Math.hypot(this.x,this.y,this.z);
},
cross(p){
var p1 = this.clone();
p1.x = this.y * p.z - this.z * p.y;
p1.y = this.z * p.x - this.x * p.z;
p1.z = this.x * p.y - this.y * p.x;
return p1;
},
dot(p){
return this.x * p.x + this.y * p.y + this.z * p.z;
},
isZero(){
return Math.abs(this.x) < small && Math.abs(this.y) < small && Math.abs(this.z) < small;
},
clone(){
return Object.assign({
x : this.x,
y : this.y,
z : this.z,
},pointFunctions);
}
}
function createPoint(x,y,z){
if(y === undefined){ // quick add overloaded for DOMPoint
y = x.y;
z = x.z;
x = x.x;
}
return Object.assign({
x, y, z,
}, pointFunctions);
}
function createRay(p1, p2){
return { p1, p2 };
}
function createFace(p1, p2, p3){
return { p1,p2, p3 };
}
// Returns the x,y coord of ray intercepting face
// ray is defined by two 3D points and is infinite in length
// face is 3 points on the intereceptin plane
// For correct intercept point face p1-p2 should be at 90deg to p1-p3 (x, and y Axis)
// returns unit coordinates x,y on the face with the origin at face.p1
// If there is no solution then returns undefined
function intersectCoord3DRayFace(ray, face ){
var u = face.p2.clone().sub(face.p1);
var v = face.p3.clone().sub(face.p1);
var n = u.cross(v);
if(n.isZero()){
return; // return undefined
}
var vr = ray.p2.clone().sub(ray.p1);
var b = n.dot(vr);
if (Math.abs(b) < small) { // ray is parallel face
return; // no intercept return undefined
}
var w = ray.p1.clone().sub(face.p1);
var a = -n.dot(w);
var uDist = a / b;
var intercept = ray.p1.clone().add(vr.mul(uDist)); // intersect point
var uu = u.dot(u);
var uv = u.dot(v);
var vv = v.dot(v);
var dot = uv * uv - uu * vv;
w = intercept.clone().sub(face.p1);
var wu = w.dot(u);
var wv = w.dot(v);
var x = (uv * wv - vv * wu) / dot;
var y = (uv * wu - uu * wv) / dot;
return {x,y};
}
Okay, so I got most of this done. Here is the code:
function gi(a) {
return document.getElementById(a)
}
gameworld = document.getElementById('GameWorld');
character = document.getElementById('character');
var oldvX = 30;
var oldvY = 30;
gameworld.addEventListener('click', function(e) {
var offsetX = e.offsetX;
var offsetY = e.offsetY;
var element = e.target;
character.style.left = offsetX + 'px';
character.style.top = offsetY + 'px';
});
setInterval(function() {
monster = gi('monster');
oldvX += 10;
oldvY += 10;
gb = gameworld.getBoundingClientRect();
cb = character.getBoundingClientRect();
oldvX += cb.left / 10;
oldvY += cb.top / 10;
if (oldvX >= (cb.left)) {
return
}
if (oldvY >= (cb.top)) {
return
}
monster.style.left = oldvX + 'px';
monster.style.top = oldvY + 'px';
}, 500);
And here is the jsfiddle:
https://jsfiddle.net/g43nxhcw/12/
As you can see, the monster is making its way towards the player via the players' getBoundingClientRect(). My dilemma is: When I move around the red square, the monster is not following my character. I am not sure how to accomplish this or if this is even possible.
You should calculate a direction vector
vx = cb.left - parseInt(monster.style.left);
vy = cb.top - parseInt(monster.style.top);
vl = Math.sqrt(vx*vx+vy*vy);
vx = vx / vl
vy = vy / vl
it always have the length 1so you can multiply with the step size
vx = vx * 10
vy = vy * 10
and make it chase you like your little friend:
monster.style.left=(parseInt(monster.style.left) + vx) +'px';
monster.style.top=(parseInt(monster.style.top) + vy)+'px';
there is something wrong with your detection of collision also..
but I wont cover that here
https://jsfiddle.net/g43nxhcw/13/
I have a DIV that I rotate based on the mouse position, here's the fiddle:
http://jsfiddle.net/JqBZb/550/
And the JS:
var img = $('.image');
if(img.length > 0){
var offset = img.offset();
function mouse(evt){
var center_x = (offset.left) + (img.width()/2);
var center_y = (offset.top) + (img.height()/2);
var mouse_x = evt.pageX; var mouse_y = evt.pageY;
var radians = Math.atan2(mouse_x - center_x, mouse_y - center_y);
var degree = (radians * (180 / Math.PI) * -1) + 90;
img.css('-webkit-transform-origin', '15px 50%');
img.css('-moz-transform', 'rotate('+degree+'deg)');
img.css('-webkit-transform', 'rotate('+degree+'deg)');
img.css('-o-transform', 'rotate('+degree+'deg)');
img.css('-ms-transform', 'rotate('+degree+'deg)');
}
$(document).mousemove(mouse);
}
First of all I say the code is taken from another post. The problem is that I want to limit the rotation of the DIV 75 degrees.
I want to make that the DIV can't point behind itself. Thanks in advance!
just add the following before you set the css:
if( Math.abs(degree) >= 75) { return; }
http://jsfiddle.net/JqBZb/551/