How to animate between 2 scales in a zoom function in react? - javascript

I want to transition when my state changes. The result I currently have is that my entire component is reload during zoom, I would like a smoother zoom. How can I do this?
I use react-tappable for detect pinch.
Here is my function :
// Zoom on pinch until maxScale, else resize to initial scale
handlePinchStart = () => {
const { scale, maxScale } = this.state;
if (scale <= maxScale) {
this.setState(state => ({ scale: state.scale + 0.5 }));
} else {
this.resize();
}
}

You can add or remove CSS class to your element which you want to zoom in or out, and do this according to your state. For example,
return (
<div className={scale <= maxScale ? "my-element zoom-in" : "my-element"}>My Element</div>
)
It simply says, if your scale state is less than or equal to the maxScale, then make the className of your element my-element zoom-in, else make it just my-element. And in CSS, these classes can be like this:
.my-element {
/* Some stylings for your element */
transition: transform 0.5s;
}
.my-element.zoom-in {
transform: scale(2);
}
As long as your scale state is <= maxScale, your element will have .zoom-in CSS class attached to it. And .zoom-in class simply makes your element's scale doubled. And thanks to putting transition: transform 0.5s; in your actual element's class(.my-element), it will grow smoothly and when scale is bigger than myScale, .zoom-in class will be removed, your element will only have .my-element class, and shrink back to its original size smoothly.

Related

Why can't I undo transform:scale() with the formula 1/a?

I am trying to write some JavaScript code that scales a div element using the transform:scale() CSS property, and then un-scales it so that the element returns to its original size. I thought that if I scale the element by applying transform:scale(a), I could un-scale it by applying transform:scale(1/a), since (element x a) x (1 / a) = element. However, that does not seem to work. Why not?
function scale_up() {
document.getElementById("div").style.transform = "scale(3)";
}
function scale_down() {
document.getElementById("div").style.transform = "scale(0.33)";
}
setTimeout(scale_up, 3000)
setTimeout(scale_down, 6000) /* this should return the div to its original size, but it doesn't */
#div {
height: 30px;
width: 30px;
border-style: solid;
}
<body>
<div id="div"></div>
</body>
CSS is a declarative language, and thus applying new properties on a given element or selector replaces existing ones with the same property name; properties are not added or multiplied when you set them again.
CSS transforms, building upon this, work the same way: setting the transform to scale(3) and then setting it to scale(0.33) has the same effect as just setting it to scale(0.33): the latter transform replaces the former.
Applying this principle, to undo the transform you can simply remove the CSS property that applies it to your element; you can do this by simply setting the property to an empty string, as per this StackOverflow answer. Alternatively, in this case, you can simply set a scale of 1:
function scale_up() {
document.getElementById("div").style.transform = "scale(3)";
}
function scale_down() {
document.getElementById("div").style.transform = "";
// This would also work:
//document.getElementById("div").style.transform = "scale(1)";
}
setTimeout(scale_up, 3000)
setTimeout(scale_down, 6000)
#div {
height: 30px;
width: 30px;
border-style: solid;
}
<body>
<div id="div"></div>
</body>
Applying a css rule doesn't add up, it replace each other.
So first, your scale_up apply a transform: scale(3), your element is scaled x3 from his original size.
Then your scale_down apply a transform: scale(0.33), your element is scaled /3 from his original size.
To set it back as normal, apply a transform: scale(1);
scale doesn't apply any permanent effects, you only need to revert to the default scale (100%):
document.getElementById("div").style.transform = "scale(1)";

GSAP/CSS transform origin and glitchy hover effect/ SVG Regions

I am attempting to animate an svg doughnut circle with gsap. After much testing with code and layering, I am stumped with a glitchy hover effect (which I tried to resolve with pointer events) and the transform origin is only applied to a few of the expanded tabs. I am wondering if this might be that the tabs may have a different bounding box?
Comments added per request:
Side Note: I've tried applying fill-box to entire svg instead, I'm wondering if I need a parent layer thats an exact square so I can apply the transform origin for the child "expandtabs" to the center of that?
I assumed I needed to iterate through an array of both to have the tabs correspond. Unless the tabs were children of each other?
TLDR; Tabs are not scaling from center of circle, and glitchy hover effect
CodePen Example
.expandtab {
pointer-events: none;
transform: fill-box;
transform-origin: -15px 25%;
}
Javascript:
const subTabs = gsap.utils.toArray(".subtab");
const expandTabs = gsap.utils.toArray(".expandtab");
const tl = gsap.timeline({ defaults: { duration: .05, } });
tl.set(expandTabs, {
visibility: "hidden",
opacity: 0,
scale: 0,
});
subTabs.forEach((subTab, index) => {
let expandTab = expandTabs[index];
// Event Listener Hover on
subTabs[index].addEventListener("mouseover", (event) => {
console.log("you clicked region number " + index);
tl.to(expandTab, {
visibility: "visible",
opacity: 1,
scale: 1,
});
});
// Event Listener Hover off
subTabs[index].addEventListener("mouseout", (event) => {
console.log("you exited region number " + index);
tl.to(expandTab, {
opacity: 0,
scale: 0,
visibility: "hidden",
});
});
});
About the glitchy hover effect, the mouseenter and mouseleave will do the job better. mouseover is firering way to much...
For the "growing" effect, it is more complex. The transform-origin CSS property won't be enought. Any way, you will need different values for each five parts of the circle.
Additionnaly, you will need to adjust a transition to "fit" or "keep" the inner part of the circle in place. I suggest you to look at the fromTo method of GSAP. That will allow you to specify explicitely the starting and the landing coordinates.
Be patient! ;)

Any way to update the duration of an animation set via element.animate without restarting the animation?

I'm developing a game engine in HTML5. Characters are div elements using an animated sprite for background. As sprite animation have fluid parameters and must be set by code, they can't be predefined in a static CSS definition, thus I use element.animate to set sprite animations to a given row at a given speed knowing my scales and frame counts.
// Applies the given frame and animation to the sprite
// Frame is an angle, clockwise direction: 0 = up, 1 = right, 2 = down, 3 = left
set_animation(frame, duration) {
const scale_x = this.settings.sprite.scale_x * this.settings.sprite.frames_x;
const pos_y = this.settings.sprite.scale_y * -frame;
// Cancel the existing animation
if(this.data_actors_self.anim) {
this.data_actors_self.anim.cancel();
this.data_actors_self.anim = null;
}
// Play the animation for this row or show the first frame if static
if(duration > 0) {
this.data_actors_self.anim = this.element.animate([
{
backgroundPosition: px([0, pos_y])
}, {
backgroundPosition: px([scale_x, pos_y])
}
], {
duration: duration * 1000,
direction: "normal",
easing: "steps(" + this.settings.sprite.frames_x + ")",
iterations: Infinity
});
this.data_actors_self.anim.play();
} else {
this.element.style.backgroundPosition = px([0, pos_y]);
}
}
Obviously that's a snippet from an actor class function: this.element is the div, this.settings is an object with parameters to be used who's names should make sense in this context, the px() function is a simple converter to turn arrays into pixel strings for HTML (eg: [0, 0] to "0px 0px").
The issue I'm having: While I can always run this function to set a new animation, I want the ability to change the speed of the animation without resetting it. It doesn't need to be a smooth transition, for all I care the new speed can be applied at the next iteration... I only want to avoid a visual snap or any kind of reset upon applying the change. Once an animation is set, I have no idea how to access and update its duration parameter. Does anyone have any suggestions?
When using console.log on this.data.anim I'm rightfully told it's an animation object. I tried using JSON.stringify to get more information but nothing relevant is printed. this.data.anim.duration returns undefined so the setting must be stored under some other property. Even if I know that property, I'd like to be sure web browsers will agree with me changing it like this.data.anim.options.duration = new_duration.
You can wait for the end of an iteration before changing the animation duration if that is what is required.
This snippet only sets an event listener for animationiteration event when you click the button to increase the speed.
function upthespeed() {
const div = document.querySelector('div');
div.addEventListener('animationiteration', function() {
div.style.animationDuration = '1s';
});
document.querySelector('button').style.display = 'none';
}
div {
width: 10vmin;
height: 10vmin;
background-color: magenta;
animation: move 10s linear infinite;
}
#keyframes move {
0% {
transform: translateX(50vw);
}
50% {
transform: translateX(0);
}
100% {
transform: translateX(50vw);
}
}
<div></div>
<button onclick="upthespeed()">Click me to increase the speed at the end of the next iteration (you may have to wait!)</button>
The value for the animation duration isn't in the Animation object itself but in the CSS animation-duration property for the Element: so this.data_actors_self.style.animationDuration = new_duration will do the job. It will however restart the animation if it is being played, but if I understand correctly that isn't a problem for you.
Edit: To change the animation's duration without restarting it, all you have to do is set the value of anim.startTime to what it was before. For example:
const startTime = anim.startTime;
this.data_actors_self.style.animationDuration = new_duration
anim.startTime = startTime;

Dynamically adjust a div's css zoom to fit container's size

Suppose such html code:
<editor>
<tree></tree>
</editor>
In my application, the tree is used to store user's input, for example:
'123'
'1111111111111111111111111111111111111111111111111111111'
So overflow is possible if text is too long.
I'd like to apply a css 'zoom' style to tree, to ensure it's size is smaller than editor.
How can I calculate the prefect zoom, using JavaScript?
You can effectively just scale it down step by step until it fits in the container.
Effectively this is:
Styling the elements so they will naturally overflow
Stepping down the scale 5% at a time
Stopping once the child element is smaller than it's parent
function calcSize() {
// The elements we need to use and our current scale
var editor = document.getElementById("editor")
var tree = document.getElementById("tree")
var scale = 1;
// Reset the initial scale and style incase we are resizing the page
tree.classList.add("loading");
tree.style.transform = "scale(1)";
// Loop until the scale is small enough to fit it's container
while (
(editor.getBoundingClientRect().width <
tree.getBoundingClientRect().width) &&
(scale > 0) // This is just incase even at 0.05 scale it doesn't fit, at which point this would cause an infinate loop if we didn't have this check
) {
// Reduce the scale
scale -= 0.05;
// Apply the new scale
tree.style.transform = "scale(" + scale + ")";
}
// Display the final result
tree.classList.remove("loading");
console.log("Final scale: " + Math.round(scale * 100) / 100)
}
// Run on load and on resize
calcSize();
window.addEventListener("resize", calcSize);
#editor {
display: block;
max-width: 50%;
font-size: 20px;
border: 1px solid #000;
overflow: visible;
}
#tree {
display: inline-block;
white-space: nowrap;
/* This is important as the default scale will be relative to the overflowed size */
transform-origin: 0 50%;
}
#tree.loading {
opacity: 0;
}
<editor id="editor">
<tree id="tree" class="loading">This is some overflowing text This is some overflowing text.</tree>
</editor>
(Try viewing the snippet in fullscreen and resizing the window to see it in effect)

How can I get an element's width in px in styled-components?

Since the element can vary its width depending on the content it has, I don't know how to get its width in px.
Here is the basic, simplified, structure:
// React component
export const Navbar = ({ month }) => (
<NavbarStyled>
<div>
<span>{month}</span> //month is what varies in width
</div>
</NavbarStyled>
)
// NavbarStyled.js
export const NavbarStyled = styled.nav`
...
span:after{
...
animation: show 1s ease forwards;
}
#keyframes show{
100%{
transform: translateX(the_element's_width_in_px);
}
}
`
I've tried a lot of things with no results.
Thanks!
You can get the width by using the getBoundingClientRect() function, which will return an object of the element's attributes.
With styled components, you will need to reference the element using the useRef hook or another referencing method in order to call getBoundingClientRect().
referencedElement.getBoundingClientRect()
// Return the width by calling referencedElement.getBoundingClientRect().width

Categories