Reactjs force DOM update - javascript

I am trying to get css animations going for the height of an element via react.
CSS animations for height are hard as they require static heights.
To animate a collapse (height from x to 0) I set the height of the element to a static height (using scrollHeight) then set the height to 0 right after, and vice versa for an expand (height from 0 to x).
Unfortunately this only works sometimes. I think react is detecting that these two events are happening quickly on the same prop and only propagating the latest to the DOM which breaks my animation.
Is there a way to force react to propagate both changes to the DOM?
Code (prob not terribly interesting):
componentWillReceiveProps(nextProps){
if(nextProps.open !== this.props.open){
if(nextProps.open){
this.setState({height:0, animating: true});
}
else{
this.setState({height:this.refs.body.scrollHeight, animating: true});
}
}
}
componentDidUpdate(prevProps, prevState){
if(prevProps.open !== this.props.open){
if(prevProps.open && !this.props.open){
this.setState({height: '0'});
}
else{
this.setState({height: this.refs.body.scrollHeight});
}
setTimeout(()=>{
this.setState({animating:false});
}.bind(this), this.props.animationTime);
}
}

Related

Navbar which hides when you scroll down and appears again when you scroll up

I have explored few approaches to this, but none really seems to work exactly like I would like:
I would like that when scrolling down, navbar is moving up at the speed the user is scrolling down, like that is static at that point.
When it disappears, I would like that the bottom of it is still visible, because this is where I have a progress bar (but maybe progress bar should detach at that point and be on top of the viewport).
When you scroll up, I would like that navbar appears again, again at the speed of scrolling, like it is static, until you see the whole navbar, when it should stick to the top of the viewport.
Here is an example of behavior I would like, but not performance/experience (because behavior is implemented using scroll event, it is not smooth).
I have also attempted to use CSS transform, which would on scroll down add a class to hide the navbar, and scroll up remove the class, animating the navbar hiding/showing, but the issue with that is that animation speed is disconnected with scrolling speed.
I tried CSS sticky position as well, but it looks like I need the opposite of what it provides.
Is there some other way to make this work well?
I've looked at your problem and I think i found a simple approach.
with this simple function you can get the amount of pixels user has scrolled.
window.onscroll = function (e) {
console.log(window.scrollY); // Value of scroll Y in px
};
after user scrolls the desired amount of pixels, make the progress bar fixed top ( or position:fixed;top:0)
Checking the link you provided, it seems to work as expected (you want it to be linked to the scroll event since you want it to move as "static"). If, though, it staggers on some system due to inconsistent scroll events, you could try adding a transition property with a small enough duration. Keep in mind the this should only be enabled while the position property remains the same, otherwise when changing from "absolute" to "fixed" it would mess things up, since the coordinate origin changes.
So you can add another variable let changedPosition = false; and whenever you change the position property you can do
if (position !== "absolute") {
changedPosition = true;
} else {
changedPosition = false;
}
position = "absolute";
or
if (position !== "fixed") {
changedPosition = true;
} else {
changedPosition = false;
}
position = "fixed";
and when you apply the style do
navbar.style = `position: ${position}; top: ${navbarTop}px; transitiona: ${
changedPosition ? "none" : "100ms linear"
}`;
like https://codepen.io/gpetrioli/pen/XWVKxNG?editors=0010
You should play around a bit with the transition properties you provide, i just put some sample values there.

Handle website scroll offset based on mobile screen size

I need to make this div visible if can scroll to left ,
<div class="fade-left" [class.visible]="showFadeLeft()"></div>
public showFadeLeft() {
return this.boxToolbarElement.nativeElement.scrollWidth -
Math.abs(this.boxToolbarElement.nativeElement.scrollLeft) !==
this.boxToolbarElement.nativeElement.clientWidth;
}
this.boxToolbarElement is the container of overflowing content .
The problem is when I open site in mobile the "fade-left" div be always shown , because display size (screen zoom) make "showFadeLeft() method" return different values , how can I handle any screen size .
NOTE: you should never use a FUNCTION inside HTML. It may cause some side effects and will decrease your application performance.
Solution
<div class="fade-left" [class.visible]="isVisible"></div>
App.component.ts
isVisible = false; // initial value can be false or tru;
ngAfterViewInit(){ // This life cycle hook will make sure that nativeElement is available for further operation.
this.showFadeLeft();
}
private showFadeLeft() { // Added private keyword
this.isVisible = this.boxToolbarElement.nativeElement.scrollWidth -
Math.abs(this.boxToolbarElement.nativeElement.scrollLeft) !==
this.boxToolbarElement.nativeElement.clientWidth;
}

Issue to fire scroll events with Smooth Scrollbar plugin

I'm using the plugin Smooth Scrollbar (here).
I have an animation on my fixed header (outside the smooth scrollbar container) and triggered when user starts scrolling the smooth scrollbar container. As mentioned in the documentation (here), it's not possible that scrollbars fire scroll events. So we have to use:
scrollbar.addListener((status) => {
...
});
This is the code to animate my fixed header:
$(window).scroll(function() {
if ($(this).scrollTop() > 50){
$('.site_header').removeClass('is--large').addClass('is--small');
}
else{
$('.site_header').removeClass('is--small').addClass('is--large');
}
});
I can't figure it out how to integrate it with Smooth Scrollbar plugin. I tried various solutions to use addListener but I can't make it work.
Any help would be much appreciated!
I know it's been a while but:
const scroller = document.querySelector('.scrollable');
const bodyScrollBar = Scrollbar.init(scroller, { damping: 0.1, delegateTo: window, alwaysShowTracks: false });
bodyScrollBar.addListener( () => {
if (bodyScrollBar.scrollTop > 50){
$('.site_header').removeClass('is--large').addClass('is--small');
}
else{
$('.site_header').removeClass('is--small').addClass('is--large');
}
}
)
where .scroller is my scrolling container. Whenever you call the variable bodyScroller in the console you can see all options set and what value you can retrieve at any time. In this instance, by calling .scrollTop (NOT a function) you can retrieve its offset top value at any time.

JQuery function in ReactJS for a dynamic viewport height

so I got some help in another question on making a table's height equal to the viewport's height. Meaning, if the user resizes the screen, the table height adjusts on-the-fly to occupy the entire height of the screen. My problem now is, this is a React App and I am having a hard time converting this jquery function to React.
My function looks like this:
$(document).ready(function() {
function setHeight() {
windowHeight = $(window).innerHeight();
$('.dynamicHeight').css('height', windowHeight + 'px');
};
setHeight();
$(window).resize(function() {
setHeight();
});
});
Here is a codepen showing the behavior
And here is a screen shot of what I am trying to do
How can I build this function in React? I'm assuming it will need some modification.
Thanks in advance
NOTE: this may look like another question I made, but it is not a duplicate. It's an entirely different question related to the same issue.
In the components lifecycle, you should add a componentDidMount method. In that method, simply add a resize handler
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props){
super(props);
this.state = {
width: $(window).width(),
height: $(window).height(),
}
this.resize = this.resize.bind(this);
}
resize(){
this.setState(() => {
return {
width: $(window).width(),
height: $(window).height()
}
});
}
componentDidMount(){
window.addEventListener('resize', this.resize);
}
render(){
return (
<div>
<MyComponent width={this.state.width} height={this.state.height} />
</div>
)
}
}
So what this does is, when your Component initializes, you set the state to the width and height of the viewport (using jQuery). Then you define a method to update the state, and when your component mounts you attach an resize event listener to it and call the resize method each time the screen resizes. This ensures that your state always contains the width and height of the viewport. Then you can define the width and height of your component by passing that state to your component as props and boom! All good, everytime you resize your screen your component will match it.
If you have any questions let me know. You might need to tinker with this to get it to work (specifically the lifecycle events might not update state as often as you need to) I just whipped it up and did not test it but in theory this is exactly what you need
edit: just a thought, if you want to change the style of your components I would use a style object. So in the child component, do something like this:
let style = {
width: this.props.width + 'px',
height: this.props.height + 'px'
}
And when you render that component
<ChildComponent style={style} />

Toggle class on windows.resize

I'm using Isotope (with Packery) to create some fancy responsive grid on my website. With jQuery I'm trying to get all elements in the first row and give them some styles:
$('.grid .grid-item').filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
On large screens there are two grid-elements in a row. On small screens there is only one. To make my script responsive I've added $windows.on('resize') around my script.
$(window).on('resize', function(){
$('.grid .grid-item').filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
});
The script works quite fine, but if I resize the screen to get the small-version (grid-items with 100% width) the second grid-item still has the css-property. Is there a way to get rid of the css-style on resize?
Check it out (and resize the screen)!
The problem with the method you have implemented is that Isotope takes time to reposition each .grid-item, so at the time you are checking for items with a top of 0 they may not have finished moving into their final position.
In addition, you are not resetting the items' opacity so once an item has been given the style it doesn't change back.
And finally, the resize event is called many times during the resize and should be debounced to only actually execute the changes once the resize event has stopped. Something like:
var debouncer;
$(window).on('resize', function(){
clearTimeout(debouncer);
debouncer=setTimeout(function(){
$('.grid .grid-item').css("opacity", "1").filter(function() {
return $(this).css('top') == '0px';
}).css("opacity", "0.5");
},200);
});
https://codepen.io/anon/pen/oYMKRj
So even with the above amends you will struggle to determine which .grid-items are at the top unless you wait for Isotope to complete its animation (which is a clue to how we solve this)...
So How?
Luckily Isotope and Packery have their own custom events you can listen for. So instead of the above you can just listen for the layoutComplete event which is triggered (as you might expect) when the layout is completed...
$grid.on('layoutComplete', function(event,items){// when the layout completes
$('.grid .grid-item')// get all grid-items
.css("opacity", "1")// reset their opacity
.filter(function() {// filter to return
return $(this).css('top') == '0px'; // the items with a top of 0
})
.css("opacity", "0.5");//set the opacity of the matched items
});
https://codepen.io/anon/pen/vyzBYy

Categories

Resources