How to maintain position after changing div transform-origin? - javascript

Let's say I had the following code:
function rotate() {
const elem = document.querySelector(".rect");
elem.style.transformOrigin = "top right";
}
.rect {
width: 200px;
height: 100px;
transform: rotateZ(45deg);
background-color: #ddd;
transform-origin: center;
}
<div class="rect"></div>
<button onclick="rotate()">Rotate</button>
You'll notice that when you click the Rotate button that the rectangle's transform-origin property changes from center to top right. As a result, the rectangle moves, because it is now rotated around a different origin.
Is it possible to have the rectangle maintain its current position, even after changing the transform origin? I am looking for a way to be able to change an element's transform origin while having it not move at all afterwards.
Basically, I'm looking for a way such that I can change the transform origin, but the div would be visually unchanged. I assume this involves some sort of translation in the rotate Javascript function, but I'm unsure of the exact calculations required to achieve such an effect.

The following seems to work:
function setRectangleOrigin(elem, newOrigin) {
const compStyle = window.getComputedStyle(elem);
const prevML = parseInt(compStyle.marginLeft);
const prevMT = parseInt(compStyle.marginTop);
const r1 = elem.getBoundingClientRect();
elem.style.transformOrigin = newOrigin;
const r2 = elem.getBoundingClientRect();
elem.style.marginLeft = prevML + (r1.x - r2.x);
elem.style.marginTop = prevMT + (r1.y - r2.y);
}
Call it like this:
setRectangleOrigin(document.querySelector(".elem"), "top left");
It takes the margin left and margin top of the current rectangle and then calculates its bounding client rect. It then applies the change to the transformation origin and then immediately gets the new bounding client rect.
Then it's a matter of simply transforming the margin left via the change in position.

Related

How can i run mouseenter function more times?

How can i run mouseenter function more times?
The problem is that I want theobject to rotate every time mouseenters, but it only works once and than nothing.
I want it to gain 80deg on every hover, however right now it only gets on position of 80deg and it can't continue, how can I make it work like on every mouseenter add +80deg?
JS
const astrx = document.querySelector(".hero__asterix");
astrx.addEventListener('mouseenter', () => {
astrx.style.transform= "rotate(80deg)"
})
HTML
<img src="./assets/Lime-asterix.svg" alt="Lime Asterix Dövr" class="hero__asterix">
SASS
&__asterix
position: relative
top: 10.3rem
left: 62%
transition: 1.5s ease-in-out
This happens because rotate(80deg) rotates an object relative to the original position (that is 0 deg), not relative to the previous one
You do probably want to save degrees and increase them each time:
dg = 80;
astrx.addEventListener('mouseenter', () => {
astrx.style.transform= `rotate(${dg}deg)`;
dg += 80;
})
The mouseenter event does run more than once, the problem is that the style has already been applied to the element after the first run so there is no style change on subsequent runs.
You can keep track of the total rotation in a variable (angle) then increase angle by 80 and update the style on the element using angle.
Here's an example:
const asterix = document.querySelector(".asterix");
let angle = 0;
asterix.addEventListener("mouseenter", () => {
angle += 80;
asterix.style.transform = `rotate(${angle}deg)`;
});
.asterix {
background-color: blue;
width: 100px;
height: 100px;
}
<div class="asterix">

Rotation and Translation of Image Javascript

I have 4 images connected to each other which form a big image. My task is to rotate the big image to a certain angle that is entered by the user.
Now, to rotate the full image, I need to rotate the smaller connected images and then translate them accordingly so that the full images seems to be rotated.
But can't get the translation coordinates properly. Please help me out with that.
Also, if there is any other way to do this, you can tell me that as well.
The code can be found here -
https://jsfiddle.net/e4qp6btx/1/
document.getElementById("img1").style.transform = "translate(" + x + "px," + y + "px) rotate(" + angle + "deg)" ;
Edit -
I actually want to rotate and translate individual images rather than rotating the whole container.
I would just target and rotate the container instead. When trying on Mac Firefox, however, the smaller images seemed to get small spaces between them.
rotateImage() {
var angle = document.getElementById('angle').value || 0; //angle to be rotated by
angle = angle % 360 + 'deg';
document.querySelector('.container').style.transform = `rotate(${angle})`;
}
I would also use a CSS variable to set the angle, and then update the variable, instead of using inline style to change the transform: rotate value.
Finally, I would give the container a unique id.
Second solution.
Rotation originates from the center of the image by default. You can change this by setting transform-orgin. The following CSS will make the images rotate like they should, so you don't need to translate them. You should probably frankenstein together my two answers, because you barely need any code for your rotateImage() to work.
#img1 {
transform-origin: bottom right;
}
#img2 {
transform-origin: bottom left;
}
#img4 {
transform-origin: top right;
}
#img3 {
transform-origin: top left;
}
Do note: you mixed up the positions of #img4 and #img3, where #img4 comes before #img3, hence these images having the transform-origins switched around.
An added bonus:
I feel it's kinda wasteful to set the same value on four different elements, so I would suggest to use a CSS variable on .container to store the rotation value on all images. It would be easier to test different values in the Inspector if you do it like that.
CSS
.container {
--image-rotation: 0deg;
width:500px;
height:500px;
padding: 50px;
}
img {
width:100%;
height:100%;
transform: rotate(var(--image-rotation));
}
Javascript
rotateImage() {
const imgContainer = document.querySelector('.container');
let angle = document.getElementById('angle').value || 0; //angle to be rotated by
angle = angle % 360 + 'deg';
imgContainer.style.setProperty('--image-rotation', angle);
}
https://jsfiddle.net/mg46dz7h/

DIV with CSS resize attribute detectable in Java Script

A DIV can be resize using CSS like this
resize: both;
overflow: auto;
So, when you mouse over the DIV , you will get the resize cursor on the lower right corner.
I tried to detect the cursor change to "resize" in Java Script with no luck so far. I tried to log the cursor style with this code on a "click event" when the cursor change to "resize" like this:
console.log(document.body.style.cursor);
console.log(e.target.style.cursor);
The log result id always a " " (empty) instead of a "resize" value.
Thanks in advance for your help.
Yes, I checked, string is empty for e.target.style.cursor. So I propose to solve the issue with a hack. We know, that an area where cursor changes is about 18×18 pixels, so we should detect cursor at this zone.
const textarea = document.querySelector('textarea');
const deltaX = 18;
const deltaY = 18;
const detectionOfCursorInLowerRightCorner = (e) => {
const rect = e.target.getBoundingClientRect();
const x = e.clientX; // Position X of cursor relative to the document.
const y = e.clientY; // Position Y of cursor relative to the document.
// Calculate lower and high edges of the area where cursor changes.
const lowEdgeX = rect.width - deltaX;
const highEdgeX = rect.width;
const lowEdgeY = rect.height - deltaY;
const highEdgeY = rect.height;
const elementCursorX = x - rect.x; // Position X of cursor relative to the element.
const elementCursorY = y - rect.y; // Position Y of cursor relative to the element.
console.log(' ');
console.log(elementCursorX, elementCursorY);
if (elementCursorX > lowEdgeX && elementCursorX <= highEdgeX && elementCursorY > lowEdgeY && elementCursorY <= highEdgeY) {
console.log('here'); // Cursor is in the area.
}
};
textarea.addEventListener('mousemove', detectionOfCursorInLowerRightCorner);
Do not forget do textarea.removeEventListener('mousemove', detectionOfCursorInLowerRightCorner) on element destroy. And define your own CSS class to detect when textarea has properties resize: both; and overflow: auto;.
Thanks for your answer. Indeed, I did something approximative (I skipped the Y axis logic) similar to your solution. On mousemove in the DIV, I consider that resize will occurs if my mouse X position is between the offetWidth-15px and offsetx. If not, I can drag my DIV.
I considered also the resize observer feature, but it was getting too complicated for multiple DIV elements.
The idea is to be add drag/resize events to my DIV elements for editing purpose over a canvas using absolute positioning.
Congratulation for your concise and well formatted answer and the time spent you spent on it.

Change width of element without reflow

I have a music player with an animated bar that displays the current position in the song. It is rendered with requestAnimationFrame and works by changing the width style of the div to X%, where X is the percentage of time through the current song.
This causes huge CPU use in Chrome I believe due to the constant reflow work being done each frame. What are other options I can use to eliminate reflows and reduce CPU?
Two other requirements: this web page is a web UI over a back end music server. It's not using any HTML5 media elements. As such, the page may be loaded when the song is already partially over, so the position will not always animate between 0 and 100.
The below fiddle shows up at about 30% CPU on my machine, which seems a bit high to animate a rectangle.
var pos = 0;
var s = document.getElementById('i');
f = function() {
window.requestAnimationFrame(f);
pos += .03;
s.style.width = pos + '%';
}
f();
#i {
background-color: red;
position: absolute;
}
<div id="i">
</div>
There are a number of ways you could make a pure CSS progress bar that won’t cause a relayout, here are a few examples:
animation - http://jsbin.com/yoqabe/edit?html,css,js,output
I think one of the most performant ways would be to use an animation to control the background position of a linear-gradient. The only downside is that you can only play/pause the animation.
background-position - http://jsbin.com/veyibe/edit?html,css,js,output
If you need the ability to update the position with JS, then I would suggest updating the background-position of a gradient and applying CSS transitions, debouncing to avoid updating too quickly.
translateX: http://jsbin.com/zolurun/edit?html,js,output
You could also use CSS transforms to slide the progress bar inside of a container, which should also avoid a repaint.
These links might also be useful:
List of CSS layout, paint, and composite triggers: http://csstriggers.com
Debounce info: https://davidwalsh.name/javascript-debounce-function
You can consider using other CSS properties which don't require layout opearations, such as background-size.
And use CSS animations instead of requestAnimationFrame.
var bar = document.getElementById('i');
function playSong(currentTime, duration) {
bar.style.animationDuration = duration + 's';
bar.style.animationDelay = - currentTime + 's';
}
playSong(3, 10);
#i {
height: 1em;
background-image: linear-gradient(red, red);
background-repeat: no-repeat;
animation: bar linear;
}
#keyframes bar {
from { background-size: 0% 100%; }
to { background-size: 100% 100%; }
}
<div id="i"></div>
If you use position: absolute or position: fixed on the progress bar itself, it should prevent large reflows on the page.
Use timeupdate, The time indicated by the element's currentTime attribute has changed.
Try this:
var audio = document.getElementById("audio");
function updateProgress() {
var progress = document.getElementById("progress");
var value = 0;
if (audio.currentTime > 0) {
value = Math.ceil((100 / audio.duration) * audio.currentTime);
}
progress.style.width = value + "%";
}
audio.addEventListener("timeupdate", updateProgress, false);
#progressBar {
border: 1px solid #aaa;
color: #fff;
width: 295px;
height: 20px;
}
#progress {
background-color: #ff0000; // red
height: 20px;
display: block;
height: 100%;
width: 0;
}
<div id="progressBar"><span id="progress"></span>
</div>
<audio id="audio" controls>
<source src="http://www.w3schools.com/tags/horse.ogg" type="audio/ogg" />
<source src="http://www.w3schools.com/tags/horse.mp3" type="audio/mpeg" />Your browser does not support the audio element.
</audio>
The script you present is not very relevant to the one you desire, you animate on requestAnimationFrame but in reality you will animate every time the "song percentage" changes.
Assuming that you have a function (e.g. getSongPer()) that returns the current percentage of played song:
var oldPos, pos = 0;
var s = document.getElementById('i');
f = function() {
oldPos = pos;
pos = getSongPer();
if(oldPos !== pos){
s.style.width = pos + '%';
}
if(pos<100){
window.requestAnimationFrame(f);
}
}
f();
I didn't test it, but I expect it to be lighter, also, the performance will be affected by the precision of the percentage, e.g. there will be about 100 animation changes if you have zero digit precision and around ten times more for every digit after.
CSS:
#progress-bar {
background-color: red;
height: 10px;
width: 100%;
transform-origin: 0 0;
}
JS:
'use strict'
var progressBar = document.getElementById('progress-bar')
function setProgress(percentage) {
requestAnimationFrame(function () {
progressBar.style.transform = 'scaleX(' + percentage + '%)'
})
}
setProgress(10)
When setting the width to 100% you get a full width colored bar.
Then we can apply the scale transform to set the width of the bar without reflowing.
But oh, it scales to the middle. We can fix that by setting the origin of the transform to the top left corner using transform-origin: x y, with x and y being 0.
Then we wrap the style change in requestAnimationFrame to let the browser optimize when to apply the change.
Bam! You have a performant zero reflow progress bar.

Moving Background Image diagonally across the screen

I'm new here, so I can't comment/follow-up yet on another question that PARTIALLY provided an answer
for what I'm trying to achieve.
On this question here [Moving Background image in a loop from left to right the fantastic and very detailed answer by Jack Pattishall Jr lets me set the page background to scroll either vertically OR horizontally.
Is there any way to combine the directional code, so that the page background scrolls diagonally
(i.e. bottom left to top right)?
I've been "mutilating" Jack's code for days now, but can't figure out how to make the background scroll in 2 directions simultaneously. :-(
http://jsfiddle.net/f5WjJ/2/
updates the fiddle from Jack Pattishall Jr to update both x AND y parameters. Also set the repeat CSS to both x AND y as well.
$(function(){
var x = 0;
var y = 0;//here
setInterval(function(){
x+=1;
y-=1;//here
$('body').css('background-position', x + 'px ' + y + 'px');//here too
}, 10);
})
background-repeat: repeat;/*and also here*/
Starting from the example mentioned above, here are my changes:
html, body { height: 100%; width: 100%;}
body {
background-image: url('http://coloradoellie.files.wordpress.com/2013/10/25280-ginger-kitten-leaping-with-arms-outstretched-white-background.jpg?w=300&h=222');
background-repeat: repeat-x repeat-y; // this line could be removed entirely
}
$(function(){
var x = 0;
var y = 0;
setInterval(function(){
x+=1;
y-=1;
$('body').css('background-position', x + 'px ' + y + 'px');
}, 10);
})
Brief description of changes:
Add repeat-y to background-repeat or remove the line (we have replicated the default behavior)
Instantiate and initialize a y-position variable
Move additively on the x-axis and negatively on the y-axis to get the background to move in the desired direction
Edit the $('body') css to include the new non-static y-position
Thanks for the advice, Joseph Neathawk

Categories

Resources