I am currently trying to figure out a way to rotate an object along axis relative to the user and not relative to the object. I'm doing this with Javascript (jQuery) and CSS transform rotate.
I have built a codepen for to test it out.
var bX = 0; //bufferX (user scroll input)
var bY = 0; //bufferY (user scroll input)
var cX = 0; //currentX (effective X rotation)
var cY = 0; //currentY (effective Y rotation)
var bBX = 0; // same second rotation properties
var bBY = 0;
var cBX = 0;
var cBY = 0;
var cBZ = 0; // currentZ (effective Z rotation)
var pi = 3.1416
// Fidget animation
function fidget() {
if (!(bX + bY == 0)) {
var dX = bX * 0.1; // how much we rotate this frame
var dY = bY * 0.1;
cX = (cX + dX) % (2 * pi); // set rotation value
cY = (cY + dY) % (2 * pi);
bX -= dX; // update buffer
bY -= dY;
}
$('#one').css('transform', 'rotateX(' + cX + 'rad) rotateY(' + cY + 'rad)');
window.requestAnimationFrame(fidget);
}
function fidgetB() {
if (!(bBX + bBY == 0)) {
var dBX = bBX * 0.1;
cBX = (cBX + dBX) % (2 * pi);
var rBY = Math.cos(cBX); //Y ratio
var rBZ = Math.sin(cBX); //Z ratio
var dBY = bBY * 0.1; // deltaY
var dBZ = bBY * 0.1;
cBY = (cBY + rBY * dBY) % (2 * pi); // current
cBZ = (cBZ - rBZ * dBZ) % (2 * pi);
bBX -= dBX; // buffer
bBY -= (dBY);
}
$('#two').css('transform', 'rotateX(' + cBX + 'rad) rotateY(' + cBY + 'rad) rotateZ(' + cBZ + 'rad)');
window.requestAnimationFrame(fidgetB);
}
var init = function () {
// Fidget
requestAnimationFrame(fidget);
requestAnimationFrame(fidgetB);
// scroll detection script
!function(window,document){var prefix="",_addEventListener,support;function _addWheelListener(elem,eventName,callback,useCapture){elem[_addEventListener](prefix+eventName,"wheel"==support?callback:function(originalEvent){!originalEvent&&(originalEvent=window.event);var event={originalEvent:originalEvent,target:originalEvent.target||originalEvent.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==originalEvent.type?0:1,deltaX:0,deltaY:0,deltaZ:0,preventDefault:function(){originalEvent.preventDefault?originalEvent.preventDefault():originalEvent.returnValue=!1}};return"mousewheel"==support?(event.deltaY=-.025*originalEvent.wheelDelta,originalEvent.wheelDeltaX&&(event.deltaX=-.025*originalEvent.wheelDeltaX)):event.deltaY=originalEvent.deltaY||originalEvent.detail,callback(event)},useCapture||!1)}window.addEventListener?_addEventListener="addEventListener":(_addEventListener="attachEvent",prefix="on"),support="onwheel"in document.createElement("div")?"wheel":void 0!==document.onmousewheel?"mousewheel":"DOMMouseScroll",window.addWheelListener=function(elem,callback,useCapture){_addWheelListener(elem,support,callback,useCapture),"DOMMouseScroll"==support&&_addWheelListener(elem,"MozMousePixelScroll",callback,useCapture)}}(window,document);
window.addWheelListener(window, function (e) {
e.preventDefault();
bY -= e.deltaX / window.innerWidth;
bX += e.deltaY / window.innerWidth;
bBY -= e.deltaX / window.innerWidth;
bBX += e.deltaY / window.innerWidth;
});
};
jQuery(document).ready(function ($) {
init();
});
html, html * {
margin: 0;
padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
color:white;
text-align: center;
font-size: 6vmin;
font-family: sans-serif;
}
#intro {
position:absolute;
width: 100%;
height: 100%;
-webkit-perspective: 200vmax;
perspective: 200vmax;
-webkit-perspective-origin: 50% 50%;
perspective-origin: 50% 50%;
transform-style: preserve-3d;
}
#one {
left: 33%;
}
#two {
left: 66%;
}
.square {
background-color: black;
width: 50vmin;
height: 50vmin;
line-height: 50vmin;
top: 50%;
margin-top: -25vmin;
margin-left: -25vmin;
transform-style: preserve-3d;
position: absolute;
-webkit-backface-visibility: visible;
backface-visibility: visible;
-webkit-transform-origin: 50% 37%;
transform-origin: 50% 37%;
/* -webkit-animation: rotate 25s linear infinite;
animation: rotate 25s linear infinite; */
pointer-events: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="intro">
<div id="one" class="square">Object</div>
<div id="two" class="square">User</div>
</div>
[https://codepen.io/rsepierre/pen/XqVJKR][1]
To control the rotation of any of those square you scroll with the mousewheel/touchpad (shift+wheel to scroll Y axis. Would add touchsupport when sorted out)
The left square's rotation is relative to the object:
if you only scroll left<->right, no problem, it works. Same for up<->down.
Now if you lay the item flat (scroll 90° down<->up ) and then you try to rotate on the other axis ( left<->right ), it will rotate on itself. Which mean that to the user it will appear like a Z-axis rotation (like a clock).
The right square's rotation is my attempt to make the rotation
relative to the user :
if you scroll left<->right, no problem, it works. Same for up<->down.
if you lay the item flat again (scroll 90° down<->up ) and then you try to rotate on the other axis ( scroll left<->right ), it will rotate relative to the user.
The item rotates on the "Z" axis, but will appears like a Y axis rotation from the users perspective.
And it is actualy working :
IF you only make scroll up<->down by 90° steps and never get in between.
Even if you get a 89° angle instead of a 90° angle when you scroll up<->down, it will slowly become a mess.
My best bet for a solution :
I came to understand that 3D rotations are not commutative (the order in which you apply rotations will impact the final result, this is why only the Y-axis rotation gets screwed up, and the X-axis rotation never does).
One easy fix would be to just add each new user input into a new rotation (rotateX(100) rotateY(5) rotateY(20) rotateX(105)... indefinitly.
But obviously i'm doing this 60times per second, it would very quickly become the worste CPU/GPU leek you've seen in a while.
Because of this I believe that somehow I should do the all the math behind the scene in JS and apply only one rotate3D(X,Y,Z,angle) css rotation.
Thing is I don't know anything about the math behind rotation matrices.
I found this converter to translate 3D rotation things into others 3D rotation things, but I think I would need to understand what is what to begin to do any math.
Any help much apreciated.
Found this stackoverflow post. It's not exactly the same situation but really the same problem.
Will post updated code when I get there.
meanwhile :
Rotating CSS cube on fixed axes
Related
The problem is solved when adding angles individually and then using ttheta(without calling a function to add angles and than using ttheta), but can anyone tell about why using function here is wrong or what problem is this function causing
The issue is solved by using this:
dtransform = window.getComputedStyle(leg1, null).getPropertyValue("transform");
values = dtransform.split('(')[1].split(')')[0].split(',');
dtheta = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
dtransform1 = window.getComputedStyle(leg2, null).getPropertyValue("transform");
values1 = dtransform1.split('(')[1].split(')')[0].split(',');
dtheta1 = Math.round(Math.atan2(values1[1], values1[0]) * (180 / Math.PI));
ttheta = dtheta + dtheta1;
Instead of using function.
What I am trying to achieve is to get value of end points of an element when it is rotated from left and top of browser.
X & Y values are max-distance of end points of shoe
I get right values at some points and wrong at some. I tried to add angle from the parent element but that also don't solve the problem.
This is the related answer from which I had taken help
To check values are right or wrong I added an event to get clientX of mouse click. And values of element positions are taken when Try button is clicked.
Am I doing something wrong, any insights will be really helpful
let leg1 = document.querySelector(".Leg1Shoe")
let leg2 = document.querySelector(".Leg1Part")
let animeAll = document.querySelectorAll(".allClass")
let animePause = false
let ttheta = 0;
function getPos() {
if (!animePause) {
animeAll.forEach(e => {
e.classList.add("AnimatePaused");
})
animePause = true;
} else {
animeAll.forEach(e => {
e.classList.remove("AnimatePaused");
})
animePause = false;
}
let h, w, x, dx, tx, y, dy, ty = "";
leg1.style.outline = "1px solid red"
h = leg1.offsetHeight;
w = leg1.offsetWidth;
x = leg1.getBoundingClientRect().left;
y = leg1.getBoundingClientRect().top;
func2(leg2);
func2(leg1);
dx = (Number(h * (Math.sin(ttheta * (Math.PI / 180)))) + Number(w * (Math.cos(ttheta * (Math.PI / 180))))).toFixed(2);
dy = (Number(w * (Math.sin(ttheta * (Math.PI / 180)))) + Number(h * (Math.cos(ttheta * (Math.PI / 180))))).toFixed(2);
tx = (Number(x) + Number(Math.abs(dx))).toFixed(2);
ty = (Number(y) + Number(Math.abs(dy))).toFixed(2);
console.log("X:" + tx, "Y:" + ty);
}
function func2(e) {
let dtransform, dtheta, values = "";
dtransform = window.getComputedStyle(e, null).getPropertyValue("transform");
if (dtransform != "none") {
values = dtransform.split('(')[1].split(')')[0].split(',');
dtheta = Math.round(Math.atan2(values[1], values[0]) * (180 / Math.PI));
} else {
dtheta = 0;
};
ttheta = Number(ttheta) + Number(dtheta);
}
leg1.addEventListener('click', mousePos);
function mousePos(e) {
console.log("X:" + e.clientX, "Y:" + e.clientY)
}
.Leg1Part {
position: relative;
left: 100px;
top: 43px;
width: 20px;
height: 75px;
background-color: green;
transform-origin: top center;
animation: animateLeg1Part 5.0s linear infinite alternate;
}
#keyframes animateLeg1Part {
0% {
transform: rotate(40deg);
}
25% {
transform: rotate(25deg);
}
50% {
transform: rotate(10deg);
}
75% {
transform: rotate(30deg);
}
100% {
transform: rotate(60deg);
}
}
.Leg1Shoe {
position: absolute;
left: 0px;
top: 73px;
width: 40px;
height: 20px;
background-color: blue;
transform-origin: center left;
animation: animateLeg1Shoe 5.0s linear infinite alternate;
}
#keyframes animateLeg1Shoe {
0% {
transform: rotate(15deg);
}
50% {
transform: rotate(-20deg);
}
100% {
transform: rotate(30deg);
}
}
.AnimatePaused {
animation-play-state: paused;
}
<div class="Leg1Part allClass">
<div class="Leg1Shoe allClass"></div>
</div>
<button onclick="getPos()">Try</button>
Thanks for help in advance
This is not a simple answer that can give you a complete solution, but just an outline of the process you can follow.
You get the transformation matrices for the "leg" and the "shoe", as you already do, by calling getPropertyValue("transform") This gives you a string like this: matrix(-0.568718, 0.822533, -0.822533, -0.568718, 0, 0), this is a shortened form of a 3x3 transformation matrix:
| cos(theta) -sin(theta) 0 |
| sin(theta) cos(theta) 0 |
| 0 0 1 |
Parse the string and create a 2d array for this matrix. Note: since you don't have any translation (two zeros in the last column) you can operate on 2x2 matrices.
Multiple the transformation matrices for the "leg" and the "shoe". It's a tedious process, and you can use the mathjs library to help.
Multiply the resulting transformation matrix by the vector of each point's original coordinates. This will give you the coordinates of the point with all rotations applied.
Here are some references:
https://developer.mozilla.org/en-US/docs/Web/CSS/transform-function/matrix()
https://en.wikipedia.org/wiki/Affine_transformation
http://web.cse.ohio-state.edu/~shen.94/681/Site/Slides_files/transformation_review.pdf
https://en.wikipedia.org/wiki/Matrix_multiplication_algorithm
I have square that follows my cursor.
Its border top is red to see if the rotation is right.
I'm trying to rotate it depending on mouse movement angle. Like if mouse goes 45deg top right then square must rotate by 45deg.
The problem is that when I move my mouse slowly the square starts to rotate like crazy. But if I move my mouse fast enough square rotates pretty smooth.
Actually it's just a part of my task that I'm trying to accomplish. My whole task is to make custom circle cursor that stretches when mouse moving. The idea I'm trying to implement:
rotate circle by mouse movement angle and then just scaleX it to make stretching effect. But I cannot do it because of problem I described above. I need my follower to rotate smoothly when mouse speed is slow.
class Cursor {
constructor() {
this.prevX = null;
this.prevY = null;
this.curX = null;
this.curY = null;
this.angle = null;
this.container = document.querySelector(".cursor");
this.follower = this.container.querySelector(".cursor-follower");
document.addEventListener("mousemove", (event) => {
this.curX = event.clientX;
this.curY = event.clientY;
});
this.position();
}
position(timestamp) {
this.follower.style.top = `${this.curY}px`;
this.follower.style.left = `${this.curX}px`;
this.angle = Math.atan2(this.curY - this.prevY, this.curX - this.prevX) * 180/Math.PI;
console.log(this.angle + 90);
this.follower.style.transform = `rotateZ(${this.angle + 90}deg)`;
this.prevX = this.curX;
this.prevY = this.curY;
requestAnimationFrame(this.position.bind(this));
}
}
const cursor = new Cursor();
.cursor-follower {
position: fixed;
top: 0;
left: 0;
z-index: 9999;
pointer-events: none;
user-select: none;
width: 76px;
height: 76px;
margin: -38px;
border: 1.5px solid #000;
border-top: 1.5px solid red;
}
<div class="cursor">
<div class="cursor-follower"></div>
</div>
Following the cursor tangent smoothly isn't as simple as it first feels. In modern browsers mousemove event fires nearby at the frame rate (typically 60 FPS). When the mouse is moving slowly, the cursor moves only a pixel or two between the events. When calculating the angle, vertical + horizontal move of 1px resolves to 45deg. Then there's another problem, the event firing rate is not consistent, during the mouse is moving, event firing rate can drop to 30 FPS or even to 24 FPS, which actually helps to get more accurate angle, but makes the scale calculations heavily inaccurate (your real task seems to need scale calculations too).
One solution is to use CSS Transitions to make the animation smoother. However, adding a transition makes the angle calculations much more complex, because the jumps between negative and positive angles Math.atan2 returns when crossing PI will become visible when using transition.
Here's a sample code of how to use transition to make the cursor follower smoother.
class Follower {
// Default options
threshold = 4;
smoothness = 10;
stretchRate = 100;
stretchMax = 100;
stretchSlow = 100;
baseAngle = Math.PI / 2;
// Class initialization
initialized = false;
// Listens mousemove event
static moveCursor (e) {
if (Follower.active) {
Follower.prototype.crsrMove.call(Follower.active, e);
}
}
static active = null;
// Adds/removes mousemove listener
static init () {
if (this.initialized) {
document.removeEventListener('mousemove', this.moveCursor);
if (this.active) {
this.active.cursor.classList.add('hidden');
}
} else {
document.addEventListener('mousemove', this.moveCursor);
}
this.initialized = !this.initialized;
}
// Base values of instances
x = -1000;
y = -1000;
angle = 0;
restoreTimer = -1;
stamp = 0;
speed = [0];
// Prototype properties
constructor (selector) {
this.cursor = document.querySelector(selector);
this.restore = this.restore.bind(this);
}
// Activates a new cursor
activate (options = {}) {
// Remove the old cursor
if (Follower.active) {
Follower.active.cursor.classList.add('hidden');
Follower.active.cursor.classList.remove('cursor', 'transitioned');
}
// Set the new cursor
Object.assign(this, options);
this.setCss = this.cursor.style.setProperty.bind(this.cursor.style);
this.cursor.classList.remove('hidden');
this.cHW = this.cursor.offsetWidth / 2;
this.cHH = this.cursor.offsetHeight / 2;
this.setCss('--smoothness', this.smoothness / 100 + 's');
this.cursor.classList.add('cursor');
setTimeout(() => this.cursor.classList.add('transitioned'), 0); // Snap to the current angle
this.crsrMove({
clientX: this.x,
clientY: this.y
});
Follower.active = this;
return this;
}
// Moves the cursor with effects
crsrMove (e) {
clearTimeout(this.restoreTimer); // Cancel reset timer
const PI = Math.PI,
pi = PI / 2,
x = e.clientX,
y = e.clientY,
dX = x - this.x,
dY = y - this.y,
dist = Math.hypot(dX, dY);
let rad = this.angle + this.baseAngle,
dTime = e.timeStamp - this.stamp,
len = this.speed.length,
sSum = this.speed.reduce((a, s) => a += s),
speed = dTime
? ((1000 / dTime) * dist + sSum) / len
: this.speed[len - 1], // Old speed when dTime = 0
scale = Math.min(
this.stretchMax / 100,
Math.max(speed / (500 - this.stretchRate || 1),
this.stretchSlow / 100
)
);
// Update base values and rotation angle
if (isNaN(dTime)) {
scale = this.scale;
} // Prevents a snap of a new cursor
if (len > 5) {
this.speed.length = 1;
}
// Update angle only when mouse has moved enough from the previous update
if (dist > this.threshold) {
let angle = Math.atan2(dY, dX),
dAngle = angle - this.angle,
adAngle = Math.abs(dAngle),
cw = 0;
// Smoothen small angles
if (adAngle < PI / 90) {
angle += dAngle * 0.5;
}
// Crossing ±PI angles
if (adAngle >= 3 * pi) {
cw = -Math.sign(dAngle) * Math.sign(dX); // Rotation direction: -1 = CW, 1 = CCW
angle += cw * 2 * PI - dAngle; // Restores the current position with negated angle
// Update transform matrix without transition & rendering
this.cursor.classList.remove('transitioned');
this.setCss('--angle', `${angle + this.baseAngle}rad`);
this.cursor.offsetWidth; // Matrix isn't updated without layout recalculation
this.cursor.classList.add('transitioned');
adAngle = 0; // The angle was handled, prevent further adjusts
}
// Orthogonal mouse turns
if (adAngle >= pi && adAngle < 3 * pi) {
this.cursor.classList.remove('transitioned');
setTimeout(() => this.cursor.classList.add('transitioned'), 0);
}
rad = angle + this.baseAngle;
this.x = x;
this.y = y;
this.angle = angle;
}
this.scale = scale;
this.stamp = e.timeStamp;
this.speed.push(speed);
// Transform the cursor
this.setCss('--angle', `${rad}rad`);
this.setCss('--scale', `${scale}`);
this.setCss('--tleft', `${x - this.cHW}px`);
this.setCss('--ttop', `${y - this.cHH}px`);
// Reset the cursor when mouse stops
this.restoreTimer = setTimeout(this.restore, this.smoothness + 100, x, y);
}
// Returns the position parameters of the cursor
position () {
const {x, y, angle, scale, speed} = this;
return {x, y, angle, scale, speed};
}
// Restores the cursor
restore (x, y) {
this.state = 0;
this.setCss('--scale', 1);
this.scale = 1;
this.speed = [0];
this.x = x;
this.y = y;
}
}
Follower.init();
const crsr = new Follower('.crsr').activate();
body {
margin: 0px;
}
.crsr {
width: 76px;
height: 76px;
border: 2px solid #000;
border-radius: 0%;
text-align: center;
font-size: 20px;
}
.cursor {
position: fixed;
cursor: default;
user-select: none;
left: var(--tleft);
top: var(--ttop);
transform: rotate(var(--angle)) scaleY(var(--scale));
}
.transitioned {
transition: transform var(--smoothness) linear;
}
.hidden {
display: none;
}
<div class="crsr hidden">A</div>
The basic idea of the code is to wait until the mouse has moved enough pixels (threshold) to calculate the angle. The "mad circle" effect is tackled by setting the angle to the same position, but at the negated angle when crossing PI. This change is made invisibly between the renderings.
CSS variables are used for the actual values in transform, this allows to change a single parameter of the transform functions at the time, you don't have to rewrite the entire rule. setCss method is just syntactic sugar, it makes the code a little bit shorter.
The current parameters are showing a rectangle follower as it is in your question. Setting ex. stretchMax = 300 and stretchSlow = 125 and adding 50% border radius to CSS might be near to what you finally need. stretchRate defines the stretch related to the speed of the mouse. If the slow motion is still not smooth enough for your purposes, you can create a better algorithm to // Smoothen small angles section (in crsrMove method). You can play with the parameters at jsFiddle.
Try like this
class Cursor {
constructor() {
this.prevX = null;
this.prevY = null;
this.curX = null;
this.curY = null;
this.angle = null;
this.container = document.querySelector(".cursor");
this.follower = this.container.querySelector(".cursor-follower");
document.addEventListener("mousemove", (event) => {
this.curX = event.clientX;
this.curY = event.clientY;
});
this.position();
}
position(timestamp) {
this.follower.style.top = `${this.curY}px`;
this.follower.style.left = `${this.curX}px`;
if (this.curY !== this.prevY && this.curX !== this.prevX) {
this.angle = Math.atan2(this.curY - this.prevY, this.curX - this.prevX) * 180/Math.PI;
}
console.log(this.angle + 90);
this.follower.style.transform = `rotateZ(${this.angle + 90}deg)`;
this.prevX = this.curX;
this.prevY = this.curY;
requestAnimationFrame(this.position.bind(this));
}
}
const cursor = new Cursor();
I am writing an image viewer JS component and i'm currently stuck on the calculation for the zooming in. I figured out the zooming in or out on one point (instead of just the center) using css transforms. I'm not using transform-origin because there will be multiple points of zooming in or out, and the position must reflect that. However, that's exactly where i'm stuck. The algorithm i'm using doesn't calculate the offset correctly after trying to zoom in (or out) when the mouse has actually moved from the initial position and I can't for the life of me figure out what's missing in the equation.
The goal is whenever the mouse is moved to another position, the whole image should scale with that position as the origin, meaning the image should scale but the point the mouse was over should remain in its place.
For reference, look at this JS component. Load an image, zoom in somewhere, move your mouse to another position and zoom in again.
Here's a pen that demonstrates the issue in my code: https://codepen.io/Emberfire/pen/jOWrVKB
I'd really apreciate it if someone has a clue as to what i'm missing :)
Here's the HTML
<div class="container">
<div class="wrapper">
<div class="image-wrapper">
<img class="viewer-img" src="https://cdn.pixabay.com/photo/2020/06/08/17/54/rain-5275506_960_720.jpg" draggable="false" />
</div>
</div>
</div>
Here's the css that i'm using for the component:
* {
box-sizing: border-box;
}
html, body {
width: 100%;
height: 100%;
margin: 0;
}
.container {
width: 100%;
height: 100%;
}
.wrapper {
width: 100%;
height: 100%;
overflow: hidden;
transition: all .1s;
position: relative;
}
.image-wrapper {
position: absolute;
}
.wrapper img {
position: absolute;
transform-origin: top left;
}
And here's the script that calculates the position and scale:
let wrapper = document.querySelector(".wrapper");
let image = wrapper.querySelector(".image-wrapper");
let scale = 1;
image.querySelector("img").addEventListener("wheel", (e) => {
if (e.deltaY < 0) {
scale = Number((scale * 1.2).toFixed(2));
let scaledX = e.offsetX * scale;
let scaledY = e.offsetY * scale;
imageOffsetX = e.offsetX - scaledX;
imageOffsetY = e.offsetY - scaledY;
e.target.style.transform = `scale3d(${scale}, ${scale}, ${scale})`;
image.style.transform = `translate(${imageOffsetX.toFixed(2)}px, ${imageOffsetY.toFixed(2)}px)`;
} else {
scale = Number((scale / 1.2).toFixed(2));
let scaledX = e.offsetX * scale;
let scaledY = e.offsetY * scale;
imageOffsetX = e.offsetX - scaledX;
imageOffsetY = e.offsetY - scaledY;
e.target.style.transform = `scale3d(${scale}, ${scale}, ${scale})`;
image.style.transform = `translate(${imageOffsetX}px, ${imageOffsetY}px)`;
}
});
Thinking a little, I understood where the algorithm failed. Your calculation is focused on the information of offsetx and offsety of the image, the problem is that you were dealing with scale and with scale the data like offsetx and offsety are not updated, they saw constants. So I stopped using scale to enlarge the image using the "width" method. It was also necessary to create two variables 'accx' and 'accy' to receive the accumulated value of the translations
let wrapper = document.querySelector(".wrapper");
let image = wrapper.querySelector(".image-wrapper");
let img = document.querySelector('img')
let scale = 1;
let accx = 0, accy = 0
image.querySelector("img").addEventListener("wheel", (e) => {
if (e.deltaY < 0) {
scale = Number((scale * 1.2));
accx += Number(e.offsetX * scale/1.2 - e.offsetX * scale)
accy += Number(e.offsetY * scale/1.2 - e.offsetY * scale)
} else {
scale = Number((scale / 1.2));
accx += Number(e.offsetX * scale * 1.2 - e.offsetX * scale)
accy += Number(e.offsetY * scale * 1.2 - e.offsetY * scale)
}
e.target.style.transform = `scale3D(${scale}, ${scale}, ${scale})`
image.style.transform = `translate(${accx}px, ${accy}px)`;
});
* {
box-sizing: border-box;
}
html,
body {
width: 100%;
height: 100%;
margin: 0;
}
.container {
width: 100%;
height: 100%;
position: relative;
}
.wrapper {
width: 100%;
height: 100%;
overflow: hidden;
transition: all 0.1s;
position: relative;
}
.image-wrapper {
position: absolute;
}
.wrapper img {
position: absolute;
transform-origin: top left;
}
.point {
width: 4px;
height: 4px;
background: #f00;
position: absolute;
}
<div class="container">
<div class="wrapper">
<div class="image-wrapper">
<img class="viewer-img" src="https://cdn.pixabay.com/photo/2020/06/08/17/54/rain-5275506_960_720.jpg" draggable="false" />
</div>
</div>
</div>
Preview the effect here: JsFiddle
You're missing to calculate the difference in the transform points after an element scales.
I would suggest to use transform-origin set to center, that way you can center initially your canvas using CSS flex.
Create an offset {x:0, y:0} that is relative to the canvas transform-origin's center
const elViewport = document.querySelector(".viewport");
const elCanvas = elViewport.querySelector(".canvas");
let scale = 1;
const scaleFactor = 0.2;
const offset = {
x: 0,
y: 0
}; // Canvas translate offset
elViewport.addEventListener("wheel", (ev) => {
ev.preventDefault();
const delta = Math.sign(-ev.deltaY); // +1 on wheelUp, -1 on wheelDown
const scaleOld = scale; // Remember the old scale
scale *= Math.exp(delta * scaleFactor); // Change scale
// Get pointer origin from canvas center
const vptRect = elViewport.getBoundingClientRect();
const cvsW = elCanvas.offsetWidth * scaleOld;
const cvsH = elCanvas.offsetHeight * scaleOld;
const cvsX = (elViewport.offsetWidth - cvsW) / 2 + offset.x;
const cvsY = (elViewport.offsetHeight - cvsH) / 2 + offset.y;
const originX = ev.x - vptRect.x - cvsX - cvsW / 2;
const originY = ev.y - vptRect.y - cvsY - cvsH / 2;
const xOrg = originX / scaleOld;
const yOrg = originY / scaleOld;
// Calculate the scaled XY
const xNew = xOrg * scale;
const yNew = yOrg * scale;
// Retrieve the XY difference to be used as the change in offset
const xDiff = originX - xNew;
const yDiff = originY - yNew;
// Update offset
offset.x += xDiff;
offset.y += yDiff;
// Apply transforms
elCanvas.style.scale = scale;
elCanvas.style.translate = `${offset.x}px ${offset.y}px`;
});
* {
margin: 0;
box-sizing: border-box;
}
.viewport {
margin: 20px;
position: relative;
overflow: hidden;
height: 200px;
transition: all .1s;
outline: 2px solid red;
display: flex;
align-items: center;
justify-content: center;
}
.canvas {
flex: none;
}
<div class="viewport">
<div class="canvas">
<img src="https://cdn.pixabay.com/photo/2020/06/08/17/54/rain-5275506_960_720.jpg" draggable="false" />
</div>
</div>
For more info head to this answer: zoom pan mouse wheel with scrollbars
I am creating a game for coursework, two tanks placed on a canvas with input boxes for the initial velocity and angle of the turret, then a button to fire a projectile (currently a div element in the shape of a circle), which calls a function in this case it is fire1. I have messed around for a few days and can't seem to get it to work, "bullet" is my div element.
function fire1 () {
var canvas = document.getElementById("canvas")
var bullet = document.getElementById("bullet");
bullet.style.visibility = "visible"
var start = null;
var intialVelocity = velocity1.value
var angle = angle1.value
var g = 9.81;
var progress, x, y;
function step(timestamp) {
if(start === null) start = timestamp;
progress = (timestamp - start)/1000;
x = (turret1.x + 80) + (intialVelocity*progress)
y = (turret1.y - 400) + (intialVelocity*progress)*Math.sin(angle*toRadians) - (0.5*g*(progress^2));//)
bullet.style.left = x + "px";
bullet.style.bottom = y + "px";
requestAnimationFrame(step);
}
requestAnimationFrame(step);
}
Below is my css bit of my bullet.
#bullet {
position: absolute;
left: 0;
bottom: 50%;
width: 1em;
height: 1em;
border-radius: 0.5em;
background: red;
visibility: hidden;
}
I am very new to javascript, css and html so help would be very appriciated, I'm trying to incorporate the trajectory formula will this work? I also want it to be animated so it follows a path when fired. Thanks
I fixed this a long time ago but forgot to update with solution, below is how x and y are calculated for the trajectory:
x = ((turret.anchorX + negative*(initialVelocity*progress*Math.cos(angle*toRadians)))); //x-coordinate for bullet calculated by using x=ut.
y = ((720 - turret.anchorY + (initialVelocity*progress*Math.sin(angle*toRadians)) + (0.5*g*(Math.pow(progress,2))))); //y-coordinate for bullet calculated by usnig ut+0.5at^2.
I was playing around with JavaScript/canvas and I want my objects color to depend on the distance to its center from current mouse position.This is my current function that gets color every mousemove event:
function getColorFromDistance(node1,node2){
var dist = getDist(node1,node2); //Getting distance;
var cl = (Math.round(255/dist*255)).toString(16); //this needs to be a propper formula
return "#" + cl + cl + cl; //converting to hex
}
Currently I get a blink effect when the distance gets 255.
I need a way to get the colors strength be depended on the distance, so that the further mouse is away from object the more its darken and when mouse is on the objects center its fully white.Well you get the idea.I just need the formula
The formula would be calculate the distance between the two points and get a percentage based on the maximum value (width of canvas/window)
//this would need to be recalulated on resize, but not doing it for demo
var targetElem = document.querySelector("div.x span");
box = targetElem.getBoundingClientRect(),
x = box.left + box.width/2,
y = box.top + box.height/2,
winBox = document.body.getBoundingClientRect(),
maxD = Math.sqrt(Math.pow(winBox.width/2, 2) + Math.pow(winBox.height/2, 2));
document.body.addEventListener("mousemove", function (evt) {
var diffX = Math.abs(evt.pageX-x),
diffY = Math.abs(evt.pageY-y),
distC = Math.sqrt(Math.pow(diffX, 2) + Math.pow(diffY, 2)),
strength = Math.ceil(255 - (distC/maxD*255)).toString(16),
color = "#" + strength + strength + strength;
targetElem.style.backgroundColor = color;
});
html, body { height: 100%; }
div.x { position: absolute; top: 50%; left:50%; }
span { display: inline-block; width: 20px; height: 20px; border-radius: 50%; border: 1px solid black; overflow: hidden; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<p>Test</p>
<div class="x"><span> </span></div>