I am making a todo app that has a sidebar and a todo page. I would like to make the page height to full screen and not put a fixed height.
Is it possible to do that with simplebar-react?
Use useLayoutEffect() in React, the final code for updating should look like this:
const [dimensions, setDimensions] = useState([0, 0]);
useLayoutEffect(() => {
function updateSize() {
setDimensions([window.innerWidth, window.innerHeight]);
}
window.addEventListener("resize", updateSize);
updateSize();
return () => window.removeEventListener("resize", updateSize);
}, []);
and then call dimensions for getting the height and width.
Related
What I want to achieve is smoothly scaled div container while scrolling (using mouse wheel to be strict) so user can zoom in and out.
However, my styles are "applied" by the browser only either when I scroll really slow or scroll normally and then wait about 0.2 seconds (after that time the changes are "bunched up"). I would like for the changes to be visible even during "fast" scrolling, not at the end.
The element with listener:
<div onWheel={(event) => {
console.log("wheeling"); // this console log fires frequently,
// and I want to update styles at the same rate
changeZoom(event);
}}
>
<div ref={scaledItem}> // content div that will be scaled according to event.deltaY
... // contents
</div>
</div>
My React code:
const changeZoom = useCallback((event: React.WheelEvent<HTMLDivElement>) => {
if (!scaledItem.current) return;
const newZoom = parseFloat(scaledItem.current.style.scale) + event.deltaY * 0.001;
console.log(newZoom); // logs as frequently as "wheeling" above
setCurrentZoom(newZoom);
}, []);
useEffect(() => {
if (!scaledItem.current) return;
scaledItem.current.style.scale = currentZoom.toString();
}, [currentZoom]);
useEffect(() => { // this is just for reproduction, needs to set initial scale to 1
if (!scaledItem.current) return;
scaledItem.current.style.scale = "1";
}, [])
What I have tried first was to omit all the React states, and edit scaledItem.current.style.scale directly from useCallback, but the changes took place in a bunch, after the wheeling events stopped coming. Then I moved zoom amount to currentZoom useState hook, but rerenders don't help either.
Edit:
I have also tried adding EventListener inside useEffect directly to the DOM Node:
useEffect(() => {
if (!scaledItemWrapper.current) return; // ref for wrapper of my scaled content
const container = scaledItemWrapper.current;
container.addEventListener("wheel", changeZoom);
return () => {
container.removeEventListener("wheel", changeZoom);
};
}, [changeZoom]);
Instead of setting up multiple states and observing can you try using a single state below is a working example. Try this if this works
https://codesandbox.io/s/wonderful-cerf-69doe?file=/src/App.js:0-727
export default () => {
const [pos, setPos] = useState({ x: 0, y: 0, scale: 1 });
const changeZoom = (e) => {
e.preventDefault();
const delta = e.deltaY * -0.01;
const newScale = pos.scale + delta;
const ratio = 1 - newScale / pos.scale;
setPos({
scale: newScale,
x: pos.x + (e.clientX - pos.x) * ratio,
y: pos.y + (e.clientY - pos.y) * ratio
});
};
return (
<div onWheelCapture={changeZoom}>
<img
src="https://source.unsplash.com/random/300x300?sky"
style={{
transformOrigin: "0 0",
transform: `translate(${pos.x}px, ${pos.y}px) scale(${pos.scale})`
}}
/>
</div>
);
};
Use a CSS transition
What I want to achieve is smoothly scaled div container while scrolling
The question's JavaScript
I didn't have to make changes to the posted JavaScript in my answer's code snippets, besides replacing scale with transform: scale() because it currently has incomplete browser support. Perhaps this can be written better but it does the job here, and is not the cause of the choppy behavior you observe.
Creating fluid motion from choppy input
Scroll events are by nature "bunched" because they arrive as the wheel is being turned "a notch". While that's not as true for all scrollable devices, it is for most people's mouse, so we have to deal with it for the foreseeable future. The browser also does additional bunching, in case of fast motion, but even without that the problem is already there.
So it's best to write code in a way that choppy input still results in a fluid motion, regardless of the step size. Once you have that, it automatically accounts for additional bunching by the browser.
You can add a CSS transition on the transform property to smooth out the scaling movement. It seems to work well with a value of 0.2 seconds, which I assume makes sense as it spreads the motion over the 0.2 seconds the browser is bunching up the changes in.
transition: transform 0.2s ease-out;
Performance implications
As a bonus, your app can keep rendering just 5 times a second.
Conversely, a solution that causes React to capture the maximum amount of fine grained scroll events will likely cause performance issues. A CSS transform is a lot cheaper then achieving the same effect through repeated renders.
Demonstration
You can observe the difference in the following 2 snippets.
It only runs properly if you open it full page. Otherwise it works but it will scroll the whole page too. I didn't want to make the code overly complex just to prevent that on SO.
Without transition (choppy)
const {useCallback, useEffect, useState, useRef} = React;
const minZoom = .01;
function App() {
const [currentZoom, setCurrentZoom] = useState("1");
const scaledItem = useRef();
const changeZoom = useCallback((event) => {
if (!scaledItem.current) return;
const scaleNumber = scaledItem.current.style.transform.replace('scale(','').replace(')','');
const newZoom = Math.max(minZoom, parseFloat(scaleNumber) + event.deltaY * 0.001);
console.log(newZoom); // logs as frequently as "wheeling" above
setCurrentZoom(newZoom);
}, []);
useEffect(() => {
if (!scaledItem.current) return;
scaledItem.current.style.transform = `scale(${currentZoom.toString()})`;
}, [currentZoom]);
useEffect(() => { // this is just for reproduction, needs to set initial scale to 1
if (!scaledItem.current) return;
scaledItem.current.style.transform = "scale(1)";
}, [])
return <div onWheel={(event) => {
console.log("wheeling");
changeZoom(event);
}}
>
<div class="scaled" ref={scaledItem}>
<p>Scale me up and down! (Use "Full page" link of snippet)</p>
</div>
</div>
}
ReactDOM.render(<App/>, document.getElementById('root'));
.scaled {
border: 2px solid lightgreen;
transform: scale(1);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
<div id="root2"></div>
With transition (smooth)
const {useCallback, useEffect, useState, useRef} = React;
const minZoom = .01;
function App() {
const [currentZoom, setCurrentZoom] = useState("1");
const scaledItem = useRef();
const changeZoom = useCallback((event) => {
if (!scaledItem.current) return;
const scaleNumber = scaledItem.current.style.transform.replace('scale(','').replace(')','');
const newZoom = Math.max(minZoom, parseFloat(scaleNumber) + event.deltaY * 0.001);
console.log(newZoom); // logs as frequently as "wheeling" above
setCurrentZoom(newZoom);
}, []);
useEffect(() => {
if (!scaledItem.current) return;
scaledItem.current.style.transform = `scale(${currentZoom.toString()})`;
}, [currentZoom]);
useEffect(() => { // this is just for reproduction, needs to set initial scale to 1
if (!scaledItem.current) return;
scaledItem.current.style.transform = "scale(1)";
}, [])
return <div onWheel={(event) => {
console.log("wheeling");
changeZoom(event);
}}
>
<div class="scaled" ref={scaledItem}>
<p>Scale me up and down! (Use "Full page" link of snippet)</p>
</div>
</div>
}
ReactDOM.render(<App/>, document.getElementById('root'));
.scaled {
border: 2px solid lightgreen;
transform: scale(1);
transition: transform .2s ease-out;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I wouldike to remove or hide an image when I run the application in mobile version or if max-width is set :
<ImageWrapper key={image.src}>
<img src={getImageUrl(path, image.src)} srcSet={getSrcSet(path, image.src)} alt={image.alt} />
</ImageWrapper>
try these two steps:
in your js file add className to your component as follow:
<ImageWrapper key={image.src} className="YourClass">
<img src={getImageUrl(path, image.src)} srcSet={getSrcSet(path, image.src)} alt={image.alt} />
</ImageWrapper>
Then, set display parameter based on screen size in your CSS file:
.YourClass {
display:block;
/* other properties */
}
#media only screen and (max-width: 768px) {
.YourClass {
display:none;
}
}
You can get the width of the content in javascript if you like to do this in javascript for some reason.
cons: you have to populate your code with aditional javascript
pro: if the user is using only mobile, it will not request the image which is good to prevent aditional requests. If you use css display:none it will request the image anyway.
import { useState, useEffect } from 'react';
//
const [width, setWidth] = useState(window.innerWidth);
// method to update the width size
const handleWindowSizeChange = () => {
setWidth(window.innerWidth);
};
// create a eventListener to update the width every time the user resize the window
useEffect(() => {
handleWindowSizeChange();
window.addEventListener('resize', handleWindowSizeChange);
return () => {
window.removeEventListener('resize', handleWindowSizeChange);
};
}, []);
Now you can use the width to check the size and check if the size is mobile or not.
if(width < 700) { // isMobile }
TIP: Hide the image in the render:
{width < 700 && <img src="image.jpg" />}
I'm trying to simulate Tesla's web app landing page scroll behavior using React.js and react-scroll
Assume we have 2 sections, the hero image is section 1, and within a small scroll from the user, an event is triggered it scrolls down to the next section (section 2)
My question is how could I trigger the scroll event to scroll to section 2 when the user is scrolling down while he is on section 1, similarly to the Tesla landing page.
I have achieved it using listener on offsetY and a condition if it is equivalent to 100px if (offsetY === 100), the window will be scrolled to section 2. but that only could be achieved in equivalence to 100. in other words, the window will be scrolled if and only if it meets 100px relative to the document.
any thoughts or recommendations would be useful.
function App() {
const [offsetY, setOffSetY] = useState(0);
const handleScroll = (e) => {
setOffSetY(window.pageYOffset)
};
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [])
useEffect(() => { // fires when the user scroll up or down, i.e. when the offsetY changes
if (offsetY === 100) { // scroll to section 2 when the offsety is equal to 100px
scroller.scrollTo("section2", {
delay: 100,
duration: 200,
smooth: true
})
}
}, [offsetY]);
return (
<React.Fragment>
<div id="section1" style={{height:"500px", width:"100%", color:"black"}}>
</div>
<div id="section2" style={{height:"500px", width:"100%", color:"red"}}>
</div>
</React.Fragment>
);
}
export default App;
I'm trying to scale a ReactJS component such that it expands to fit its parent div container. The component returns an
<svg/>
element with several children. The API of the react component only lets me set the svg element's CSS style. I don't have a way to give the svg element a viewbox attribute.
Is there a way for me to get the SVG to scale to fill its parent container through CSS only?
The react component for reference: link
Well, quite simple. All you need to do is combine ResizeObserver and transform: scale(). This solution was applied to stretch the svg world map into the area of dashboard widget on the prod build of app in the company where I work.
There is a gist which published with a link to JSFiddle demo.
(function() {
const parent = document.querySelector('.parent');
const item = document.querySelector('.item');
let scaleAdjust = 1.0;
const process = (container, ref) => {
const {
height: originHeight,
width: originWidth
} = ref.getBoundingClientRect();
const {
height: containerHeight,
width: containerWidth,
} = container.getBoundingClientRect();
const [height, width] = [originHeight * scaleAdjust, originWidth * scaleAdjust];
const [from, to] = height > width ? [width, containerWidth] : [height, containerHeight];
const scale = to / from;
ref.style.transform = `scale(${scale})`;
scaleAdjust = 1 / scale;
};
new ResizeObserver(() => process(parent, item)).observe(document.body);
})();
I am trying to use following code with React Native:
...
_getContentHeight() {
if (this.refs.AccordionContent) {
this.refs.AccordionContent.measure((ox, oy, width, height, px, py) => {
// Sets content height in state
this.setState({
height: this.props.expanded ? height : 0,
content_height: height
});
});
}
},
componentDidMount() {
// Gets content height when component mounts
// without setTimeout, measure returns 0 for every value.
// See https://github.com/facebook/react-native/issues/953
setTimeout(this._getContentHeight);
// tried setTimeout(this._getContentHeight.bind(this);
},
...
Now, when I debug the app in chrome, I see, that I never get to the _getContentHeight(). Now I am pretty sure that it has to do something with asynchronous calls etc. But is there a way to debug this, and see what values I get for height and content_height?
Help is needed, especially in understanding setTimeout/asynchronous function calls.
I'd suggest moving the call to measure inside componentDidMount to make sure your functionality works before abstracting it and dealing with any scoping or object reference issues that you might be facing now.
componentDidMount() {
this.refs.AccordionContent.measure((ox, oy, width, height, px, py) => {
// Sets content height in state
this.setState({
height: this.props.expanded ? height : 0,
content_height: height
});
});
}
Update:
I've wrapped your code in a react class and got the measure object to error out # https://jsfiddle.net/x7w62w99/
var Hello = React.createClass({
_getContentHeight() {
console.log(this.refs)
if (this.refs.AccordionContent) {
this.refs.AccordionContent.measure((ox, oy, width, height, px, py) => {
// Sets content height in state
this.setState({
height: this.props.expanded ? height : 0,
content_height: height
});
});
}
},
componentDidMount() {
setTimeout(this._getContentHeight);
},
render: function() {
return <div ref='AccordionContent'>
Hello {this.props.name}
<div id='AccordionContent'>
some stuff
</div>
</div>;
}
});
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
Try this
setTimeout(function(){
this._getContentHeight.bind(this)
})
Use This:
setTimeout({
this._getContentHeight
}, the delay you want);
SetTimeout takes two parameteres.
http://www.w3schools.com/jsref/met_win_settimeout.asp