I haven't seen a thread that handles this, at least not for React.
My case: I want to conditionally render a back to top button only when scrolling is an option. It makes no sense to have such a feature if it can't affect the page.
The only solutions I can find are in jQuery. I'm using react-scroll but couldn't find any functionality there for this.
When a scrollbar is visible then window.visualViewport.width < window.Width.
var buttonIsVisible = window.visualViewport.width < window.Width;
To check if scrollbar is visible in vertical appearance.
document.body.clientHeight > window.innerHeight
I added this code in a useEffect.
useEffect(() => {
if (document.body.clientHeight > window.innerHeight) {
something()
}
}, [state]);
Luke.
By "scrolling is an option" I am assuming here that you mean "when the scrollbar is visible."
As far as I am aware, there is not any way to check for scrollbar visibility using the React API. There is the DOM boolean window.scrollbars.visible; however, I have not had luck with this. It seems to always return true whether a scrollbar is visible or not. The following approach may work for you:
You could set an event listener for onScroll and check window.scrollY. If window.scrollY > 0, then you could conditionally render the button. If window.scrollY is 0, then the page is already scrolled to the top and there is no need to display the button.
Depending on the design of your web app, checking once for scrollbar visibility (e.g., on componentDidMount) may not be the best option, since some DOM elements may continue to load after the component initially mounts and the height of the page may change.
I hope this is helpful.
If you have a wrapper around the element that has the scroll you can detect the width difference.
<div className="wrapper">
<div className="scrollingContent">
Very long content here
</div>
</div>
const scrollBarWidth = this.wrapper.clientWidth - this.scrollingContent.clientWidth;
this.setState({ scrollBarWidth });
Most of the time (depending on edge cases where elements are sized differently). You can use an element ref to check if the scrollWidth is greater than the current width (or height for vertical scroll). The ref might not update scroll properties with useEffect hence why you need state in the dependencies array. Plus you will likely want to add a window resize event listener to run the same code.
const ref = useRef(null);
const [hasScrollBar, setHasScrollBar] = useState(false);
useEffect(() => {
function updateState() {
const el = ref.current;
el && setHasScrollBar(el.scrollWidth > el.getBoundingClientRect().width);
}
updateState();
window.addEventListener('resize', updateState);
return () => window.removeEventListener('resize', updateState);
}, [state]);
<div ref={ref} style={{ overflowX: 'auto' }}>
{state}
</div>
Related
I have two images. When I click the image container, a state is changed, and another image is revealed, while the other one disappears - like this:
const Image= ({image1, image2}) => {
const [image, setImageState] = useState(true);
return (
<div className="image-container" onClick={ () => setImageState(!image)}>
<img src={image ? "/images/" + image1 : "/images/" + image2} alt="" />
</div>
)
}
So this works as intended. When I click, the state changes, and so does the image. However, I would like there to be a fade in/out effect when the state changes. How is this achieved ?
I'm not sure if it's possible to animate via CSS the src attribute of an image element (I personally prefer to do it via CSS if possible). However you can animate its opacity property. And you'll need two elements for that - one for the fade-out effect and one for the fade-in. Here's a sandbox. The situation is very similar to this question, there you can see how to put images on top of each other (the one hidden).
Some things to note:
I've pre-set the two images' src attributes and don't set them to
nulls or some falsy values so that we don't see broken image icon while animating (could
occur if you have your state separated among multiple useState
hooks). In my case I batch changes at once to have only a single render and avoid any flashes of broken image icon.
Browser will do the animation upon class change and of course if the
property in this class can be animated. opacity is a CSS property which can be
animated.
For your case you'll probably need to set the src dynamically in your
handler so I'd recommend you make sure you don't cause multiple
renders/paints to avoid visual inconsistencies, so I think you should batch your state updates at once.
In my case the images are next to each other, but if you want to first fade-out the first image and then fade-in the second, you'll need to synchronize animation with transition-delay property.
You could some Animation library like GSAP and accomplish this, as this is difficult to accomplish by css alone.
https://codepen.io/rohinikumar4073/pen/mdVYbrm?editors=1111
function toggleImage() {
let tween = TweenLite.to(".img", 2, {
opacity: 0,
onComplete: function() {
let image = document.querySelector(".img")
if (image.src === "https://via.placeholder.com/350") {
image.src = "https://via.placeholder.com/150";
} else {
image.src = "https://via.placeholder.com/350";
}
let tween2 = TweenLite.to(".img", 2, {
opacity: 2
});
}
});
}
<script src=https://cdnjs.cloudflare.com/ajax/libs/gsap/3.4.2/gsap.min.js"></script>
<div onclick="toggleImage()">
<img class="img" id="img1" src="https://via.placeholder.com/150" />
</div>
Replication Steps:
https://developer.microsoft.com/en-us/fluentui#/controls/web/scrollablepane
In the 'DetailsList Locked Header' scroll down to a non zero position exceeding the first page and enter 'sed' in the 'filter by name'. This updates the detailsList and the scrollbar does not go to the initial position.
Bug/Ask:
As we scroll down to let's say a position of 50 and now if there is an update to the details list with say suppose 350. The scrollbar is not returning to the starting position and it rerenders to a random position close to the top(in my case).
Approach/Temporary Hack:
Going through the code, I noticed that the initial scroll position of the scrollablepane is being refreshed only when the 'initialScrollPosition' props change in the implementation.
I tried changing the 'initialScrollPosition' prop in my component whenever the detailList updates by setting it to either 0 or 1.
Debugging this, the props for the initialScrollPosition did not change inside the ScrollablePane component did not change.
This did not work and the scrollable pane is still not set to the start.
Has anyone found a workaround or a solution for this?
I Had the same issue
I'm also using infinitive scroll(react-infinite-scroller) to load the data.
what I finally done , was replace scrollablepane by css.
(Function handleGetNext just loading new data, and variable dataToRender I keep on the state to make concatenation between old state and new)
<div>
{handleSelect()}
<div style={{ overflowY: "scroll", height: 500 }} id="containers">
<InfiniteScroll
pageStart={0}
loadMore={() => {
handleGetNext(overviewData.page);
handleSelect();
}}
hasMore={!loadingNext && !!overviewData && overviewData.page < overviewData.totalPages}
initialLoad={false}
useWindow={false}
>
<ShimmeredDetailsList items={dataToRender} enableShimmer={overviewLoading} .........../>
</InfiniteScroll>
</div>
</div>
And later what is tricky you need to set scroll position, depends where you want to scroll.
const handleSelect = () => {
const elemToScrollTo = document.getElementById("containers");
if (!!elemToScrollTo) {
elemToScrollTo.scrollTop = 2100;//example
}
};
As said in the title. I want to create a React component that will give me a possibility to resize its width by dragging - just like windows in Windows operating system. What is actually the best approach to handle this issue ?
EDIT
I included my current approach to the subject of the matter:
First I placed a "dragger" element in the top-right corner of my container. When i press mouse down on that element i want to create a mousemove event listener which will modify the containerWidth in respect to the X coordinate of the cursor relative to the initial X position of the edge of the container. I already have that event listener firing and logging me the coordinates after holding down the mouse button but unfortunatelly for some reason the event is not being removed after the mouse is unpressed(mouseUp event) which is not what i intended. Any suggestions appreciated, also those about some issues i might expect in the future related to this topic. Thanks.
type Props = MasterProps & LinkStateToProps & LinkDispatchToProps;
const Test3 = (Props: Props) => {
const [containerWidth, setContainerWidth] = React.useState(640)
const [isBeingStretched, setIsBeingStretched] = React.useState(false);
const masterRef = React.useRef(null);
const logMousePosition = React.useCallback((event:MouseEvent)=>{
console.log(event.clientX);
},[])
const handleMouseDown=()=>{
document.addEventListener('mousemove', logMousePosition);
masterRef.current.addEventListener('mouseup', ()=>{
document.removeEventListener('mouseup', logMousePosition)
})
}
const handleMouseUp = () => {
document.removeEventListener('mousemove', logMousePosition);
}
return (
<div className="master-wrapper" ref={masterRef}>
<div className="stretchable-div" style={{ width: `${containerWidth}px` }}>
<div className="dragger-wrapper">
<h2>This is supposed to change width</h2>
<div className="dragger"
id="dragger"
onMouseDown={handleMouseDown}
onMouseUp={handleMouseUp}/>
</div>
</div>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(Test3);
I'd never done something like this before so I decided to give it a go, and it ended up being quite straightforward to implement with React state management. I can see why you might not know where to start if you are new to React, and that's ok, although two things to note before I go through my solution:
Statements such as document.getElementById or document.addEventListener are not going to function as intended anymore. With React, you are manipulating a virtual DOM, which updates the actual DOM for you, and you should aim to let it do that as much as possible.
Using refs to get around this fact is bad practice. They may act in a similar way to the statements mentioned above but that is not their intended use case. Read up on what the documentation has to say about good use cases for ref.
Here's what the JSX portion of my demo looks like:
return (
<div className="container" onMouseMove={resizeFrame} onMouseUp={stopResize}>
<div className="box" style={boxStyle}>
<button className="dragger" onMouseDown={startResize}>
Size Me
</button>
</div>
</div>
);
We're going to need three different events - onMouseDown, onMouseMove and onMouseUp - to track the different stages of the resize. You already got this far in your own code. In React, we declare all these as attributes of the elements themselves, although they are not actually in-line functions. React adds them as event listeners for us in the background.
const [drag, setDrag] = useState({
active: false,
x: "",
y: ""
});
const startResize = e => {
setDrag({
active: true,
x: e.clientX,
y: e.clientY
});
};
We'll use some state to track the resize as it is in progress. I condensed everything into a single object to avoid bloat and make it more readable, although this won't always be ideal if you have other hooks like useEffect or useMemo dependent on that state. The first event simply saves the initial x and y positions of the user's mouse, and sets active to true for the next event to reference.
const [dims, setDims] = useState({
w: 200,
h: 200
});
const resizeFrame = e => {
const { active, x, y } = drag;
if (active) {
const xDiff = Math.abs(x - e.clientX);
const yDiff = Math.abs(y - e.clientY);
const newW = x > e.clientX ? dims.w - xDiff : dims.w + xDiff;
const newH = y > e.clientY ? dims.h + yDiff : dims.h - yDiff;
setDrag({ ...drag, x: e.clientX, y: e.clientY });
setDims({ w: newW, h: newH });
}
};
The second piece of state will initialise and then update the dimensions of the element as its values change. This could use any measurement you want although it will have to correlate to some CSS property.
The resizeFrame function does the following:
Make the properties of drag easily available via destructuring assignment. This will make the code more readable and easier to type.
Check that the resize is active. onMouseMove will fire for every pixel the mouse moves over the relevant element so we want to make sure it is properly conditioned.
Use Math.abs() to get the difference in value between the current mouse position and the saved mouse position as a positive integer. This will save us from having to do a second round of conditional statements.
Use turnary statements to either add or subtract the difference from the dimensions, based on whether the new mouse position is greater or less than the previous on either axis.
Set the states with the new values, using the spread operator ... to leave the irrelevant part of drag as it is.
const stopResize = e => {
setDrag({ ...drag, active: false });
};
const boxStyle = {
width: `${dims.x}px`,
height: `${dims.y}px`
};
Then we simply set the activity of the drag state to false once the user is finished. The JS style object is passed to the element with the state variable in place so that is taken care of automatically.
Here is the codesandbox with the finished effect.
One of the drawbacks to doing things this way is that it basically requires you to have that mouseMove event listener assigned to the largest site container, because the mouse is not going to stay within the bounds of the box during the resize. That could be an issue if you want to have multiple elements with the same functionality, although nothing that you can't solve with good state management. You could probably fine tune this so that the mouse always stays on the drag element, although that would require a more complex implementation.
I'm trying to make a small sprite-based game with ReactJS. The green dragon (was taken from HMMII) is flying across the hexagonal field and it's behavior depends on mouse clicking. The sprites change each other with speed depending on a specially chosen time constant - 170ms. More precisely: there is a div representing the dragon and it's properties (top, left, width, height and background-image) always are changing.
At the first stage of the development I've faced with irritating blinking and flickering by rerendering the image. How can avoid it?
Below are described multiple ways I've used with some previews made with Surge. The strongest effect is watched in Google Chrome but in Firefox also are troubles.
0) At first I've tried to use CSS-animation based on #keyframes, but it was no good due to fade effect. And I don't need any fade effects at all, I need rapid rerendering.
1) This is the most straightforward attempt. After clicking on a particular field, componentWillReceiveProps is creating the list of steps and then all of this steps are performing consistently. Also I've tried to use requestAnimationFrame instead of setTimeout but with the same troubles.
makeStep() {
const {steps} = this.state;
this.setState((prevState, props) => ({
steps: steps.slice(1),
style: {...}
}));
}
render() {
const {steps, style} = this.state;
steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
this.props.endTurn();
return (<div id="dragon" style={style}></div>);
}
Here is the result: http://streuner.surge.sh/ As you can see, dragon is often disapearing by launching and landing, it fly with skipping some sprites.
2) I've tried to test method describen in article:
https://itnext.io/stable-image-component-with-placeholder-in-react-7c837b1ebee
In this case I've changed my div with background-image to other div containing explicit img. At first, this.state.isLoaded is false and new sprite will not appear. It appears only after the image has been loaded with onLoad method. Also I've tried to use refs with attempt watch for complete-property of the image but it's always true - maybe because size of the image is very small.
setLoaded(){
this.setState((prevState, props) => ({
isLoaded: true
}));
}
render() {
const {isLoaded, steps, style} = this.state;
if(isLoaded) {
steps.length ? setTimeout(this.makeStep, DRAGON_RENDER_TIME):
this.props.endTurn();
}
return (<div id="wrap" style={{top:style.top, left:style.left}} >
<img id="dragon" alt="" src={style.src} onLoad={this.setLoaded}
style={{width:style.width,
height: style.height,
visibility: isLoaded ? "visible": "hidden"}}/>
</div>);
}
Here is the result: http://streuner2.surge.sh/ There's no more sprite skipping but the flickering effect is much stronger than in first case.
3) Maybe it was my best attempt. I've read this advice: https://github.com/facebook/react-native/issues/981 and decided to render immediately all of the step images but only the one with opacity = 1, the others have opacity = 0.
makeStep(index) {
const {steps} = this.state;
this.setState((prevState, props) => ({
index: index + 1,
steps: steps.map( (s, i) => ({...s, opacity: (i !== index) ? 0: 1}))
}));
}
render() {
const {index, steps} = this.state;
(index < steps.length) ?
setTimeout(() => this.makeStep(index), DRAGON_RENDER_TIME):
this.props.endTurn();
return ([steps.map((s, i) =>
<div className="dragon" key={i} style={s}></div>)]);
}
It's possible to see the result here: http://streuner3.surge.sh/ There's only one flickering by starting new fly with rerendering all sprites. But the code seems to me more artificial.
I would like to emphasize that the behavior always depends on browser, in Firefox it's much better. Also there are differences with variety of flys in the same browser: sometimes there's no flickering effect but in most of cases it unfortunately is. Maybe I don't understand any basic notion of rerendering images in browser.
I think you should shift your attention from animation itself and pay more attention to rerendering in React, each time when you change Image component state or props it is rerendering. Read about lifecycle methods and rerendering in React docs.
You change state very fast(in your case it's almost 6 times per second), so I suppose that some of the browsers are not fast enough with Image component rerendering. Try to move out of Image state variables which updates so fast and everything will be ok
I know the answer is late, but posting my answer here in case someone still wants to find a solution and because I've found this drives some traffic.
A simple workaround is to add a CSS transition property to the image like the below:
transition: all .5s;
it does not prevent the image re-rendering, but at least it does prevent the image flickering.
I have a few components and some of them are sized with px. The others I want to be variable size. However, the ones that are variable size are not a constant percentage of the page because of the components with a fixed px height. So I want a component to be about 80% of the screen height('80vh') minus the height of the other component. I was hoping to use something like
style={{height:'80vh-40px'}}
but that does not work.
I found this page which gets close but that my program does not recognize that calc function. Do I need to require it from some library maybe?
Any ideas on how to make this work?
Thanks!
I use syntax like this in my React.js components:
<div style={{height: 'calc(100vh - 400px)'}}></div>
it works for me.
Inside CSS code with LESS preprocessor I use the same syntax, but not equal:
.right_col {
height: calc(~"100vh - 400px");
}
In my experiments, I found that symbol "~" doesn't work in React.js components.
A way to solve it is by using style={} in your component.
const styles = {targetDiv: { height: 'calc(100vh - Xpx)'}}
then...
<div style={styles.targetDiv}>
...
</div>
Note - you can get the window.innerHeight (see http://ryanve.com/lab/dimensions/ for height and width options in javascript) and just calculate this yourself in react and set the width or height in pixels.
If you do this with height, you'd need to recalculate if it changes
state = {
windowHeight: window.innerHeight
}
componentWillMount () {
window.addEventListener('resize', this.handleResize)
}
componentWillUnmount () {
window.removeEventListener('resize', this.handleResize)
}
handleResize = () => {
this.setState({ windowHeight: window.innerHeight })
}
in your div
<div style={{height: this.state.windowHeight}} > ... </div>
You wouldn't want to do this everywhere, but it's very handy for some situations. For example - setting 100% height on Android mobile web, where 100vh does not work and device buttons cover up part of the screen.