Creating a smooth fixed / sticky slideshow layout with react-intersection-observer - javascript

I am trying to build a react component with a large fixed div and a navbar below. Initially, the div should remain fixed at the top left and scroll through a number of slides as the user scrolls, before its display property reverts to absolute and it scrolls out of view. Meanwhile, the nav's position becomes sticky and scrolls to the top of the window where it remains.
I've managed to build something hacky using react-intersection-observer and a number of hidden 'ghost' elements which allow the slides to cycle between opacity 0 or 1, but the outcome is not great and I feel there must be an easier way to achieve this effect. In particular it is problematic having to account for the page position when the main component becomes absolute positioned and requires a 'top' value.
Any advice is appreciated thanks. Sample code is below:
import * as React from 'react';
import { useInView } from 'react-intersection-observer';
import styled from 'styled-components';
const OuterContainer = styled.div`
height: 500vh;
`;
const Card = styled.div`
box-sizing: border-box;
position: fixed;
top: 0;
width: 100%;
height:64vh;
transition: 1s;
z-index: 5;
&.hidden {
opacity: 0;
}
&.show {
opacity: 1;
}
&.border {
position: absolute;
top: 0;
}
`;
const AltCard = styled(Card)`
position: static;
`;
const CardContainer = styled.div`
height: 64vh;
`;
const FirstCard = styled(Card)`
opacity: 1;
`;
const CardGhost = styled.div`
height:64vh;
`;
const LastGhost = styled.div`
box-sizing: border-box;
`;
const GhostContainer = styled.div`
width: 100%;
position: absolute;
top: 0;
`;
const MainContainer = styled.div`
position: fixed;
width: 100%;
top: 0;
&.moving {
position: absolute;
top: 156.5vh;
}
`;
const HeaderBar = styled.div`
top: 64vh;
display: flex;
align-items: center;
justify-content: center;
flex: 1;
height: 8rem;
background-color: #44ff44;
`;
export default function IntroSection() {
const options = { threshold: 0.66 };
const [ref0, visible0] = useInView({ threshold: 0 });
const [ref1, visible1] = useInView(options);
const [ref2, visible2] = useInView(options);
const [ref3, visible3] = useInView(options);
const [ref4, visible4] = useInView({ threshold: 0.33 });
return (
<>
<OuterContainer>
<GhostContainer>
<CardGhost ref={ref0} />
<CardGhost ref={ref1} />
<CardGhost ref={ref2} />
<CardGhost ref={ref3} />
<LastGhost ref={ref4} />
</GhostContainer>
<CardContainer>
<FirstCard
className={visible0 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightBlue' }}
/>
<Card
className={visible1 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightGreen' }}
/>
<Card
className={visible2 ? 'show' : 'hidden'}
style={{ backgroundColor: 'lightYellow' }}
/>
</CardContainer>
</OuterContainer>
<MainContainer
className={visible4 && 'moving'}
>
<AltCard
className={visible3 ? 'show' : 'hidden'}
style={{ backgroundColor: '#ffBBFF' }}
/>
<HeaderBar><h1>THIS IS THE NAVBAR</h1></HeaderBar>
</MainContainer>
</>
);
}

Related

How to overflow: 'scroll' for dynamically created components

In a react app, I dynamically create new components inside of a container. When the height of the container is exceeded by the newly created items, I want the container to become scrollable in the Y direction. I could not achieve the behavior by using overflow: 'auto'. Any suggestions?
sass:
.v-timeline{ // parent component
flex: 1;
height: 100%;
width: 100%;
flex-wrap: nowrap;
.v-timeline-container { // this is our container
.v-timeline-indicator {
z-index: 2;
background-color: rgb(0, 105, 185);
width: 3px;
position: relative;
}
.v-timeline-items { // these are the new items
.v-timeline-item {
height: 60px;
}
}
}
}
The component:
const [gridItems, setGridItems] = useState([{ id: 'firstItemID', info: 'firstItemInfo'}]);
const addRow = () => {
setGridItems([
...gridItems, {
id: uuidv4() ,
info: 'doesnt matter'
}
]);
}
return (
<>
<button onClick={addRow}>Add Row</button>
<div className='v-timeline-container' style={{ overflow: 'auto'}} >
{
gridItems.map(item => (
<div id={`thisTimeline${item.id}`} style={{ display: 'flex', overflow: 'scroll'}} key={`thisItem${item.id}`} className="v-timeline-items v-border">
<Indicator id={item.id} height={60}/>
<div>
<CustomItemContainer id={item.id} info={item.info}/>
</div>
</div>
))
}
</div>
</>
)
This is how it starts:
This is how it goes:
It seems like you didn't set height to the container.
Try to set a fixed height to container.
.v-timeline-container{
height: 100%;
}

Using React-Spring useSprings only on active button in array

I am trying to animate a position change of the active box. When the user clicks on the box, I would like the box to animate a margin-top change and the rest of the boxes to stay at 0px. When the user changes the active box, the previously active box will return to 0px and the new active box will acquire a margin-top of 50px.
I think I am nearly there but I haven't quite managed to get it to work yet. I would be really grateful if someone could help enlighten me as to what I am doing wrong.
import { useSpring, useSprings, animated } from "react-spring";
import styled from "styled-components";
const Nav = () => {
// Set Active Box
const [activeBox, setActiveBox] = useState(0);
const handleClick = (index) => {
setActiveBox(index);
};
// Boxes
const boxes = [box1, box2, box3, box4];
// useSprings
const springs = useSprings(
boxes.length,
boxes.map((box, index) => ({
marginTop: activeBox === index ? 50 : 0,
}))
);
return (
<NavContainer>
{springs.map((prop, index) => (
<StyledBox
style={prop}
key={index}
onClick={() => handleClick(index)}
currentActiveBox={activeBox === index}
>
<img src={boxes[index]} alt="" />
</StyledBox>
))}
</NavContainer>
);
};
export default Nav;
const NavContainer = styled.div`
position: absolute;
bottom: 0;
width: 200px;
height: 75px;
`;
const StyledBox = styled.div`
width: 25px;
height: 25px;
img {
width: 100%;
height: 100%;
}
`;
Thank you!
The issue was that animated was not added to the styled component!
const StyledBox = styled(animated.div)`
width: 25px;
height: 25px;
img {
width: 100%;
height: 100%;
}

Rendering multiple style classes with react styled components

In the stepper component that I am working on, I am using the React Styled Components library. Unfortunately, I can't use Material UI or another similar solution. Originally, I was using CSS which rendered all of the UI for the stepper:
{index !== steps.length - 1 && (
<div
className={`divider-line divider-line_${steps.length}`}
/>
)}
This worked, but now I have to use this library, so I converted the I've now converted the horizontal line CSS as styled-components.
styled.js
export const DividerLine = styled.div`
height: 1px;
background-color: blue;
position: absolute;
top: 20%;
left: 70%;
`;
export const DividerLine_2 = styled.div`
width: 296%;
`;
export const DividerLine_3 = styled.div`
width: 125%;
`;
export const DividerLine_4 = styled.div`
width: 70%;
`;
export const DividerLine_5 = styled.div`
width: 60%;
`;
The rest of the stepper animations work but the original code does not render the horizontal lines.
I ran into a similar problem with rendering the numbered bubbles in various states, which I resolved by creating variables to use within inline styling:
let stepColor = '#65CC34';
let stepText = '#ffffff';
let stepTextActive = '#65CC34';
Stepper.js
<StepContainer key={index}>
<StepWrapper>
<StepNumber
style={{
background: `${
step.selected ? stepColor : ''
}`,
color: `${
step.selected ? stepText : stepTextActive
}`,
}}
>
{step.completed ? <span>✓</span> : index + 1}
</StepNumber>
{index !== steps.length - 1 && (
<div
className={`divider-line divider-line_${steps.length}`}
/>
)}
</StepWrapper>
<DividerLine />
</StepContainer>
);
});
return <StepWrapper>{stepsJSX}</StepWrapper>;
}
}
However, I'm not sure how to use {`divider-line divider-line_${steps.length}`} inside of the styled component. Any suggestions?
you don't need to use multiple classes with styled-components. Here's how you can do it:
const dividerLineWidths = {
'2': '296%',
'3': '125%',
'4': '70%',
'5': '50%'
}
export const DividerLine = styled.div`
height: 1px;
background-color: blue;
position: absolute;
top: 20%;
left: 70%;
${({steps}) => steps && `
width: ${dividerLineWidths[steps.length]}
`}
`;
Then:
...
{index !== steps.length - 1 && <DividerLine steps={steps} />}
...
Styled components will dynamically generate a new className for you depending on the props being passed to it. Think of the styled-components consts as full components as opposed to blobs of css properties.

How to place the popup based on the position of another div element using javascript and react?

i want to place a popup based on the position of leftnav element. basically place this popup next to the lefnav element such that it is vertically center to leftnav element.
below is the picture of how the popup should be placed
below is my code,
function LeftNav() {
return (
<Wrapper id="left_nav">
</Wrapper>
);
}
const Wrapper = styled.div`
position: absolute;
top: 50%;
right: 16px;
display: flex;
flex-direction: column;
flex: 1;
`;
function PopUp() {
const left_nav_el = document.getElementById('left_nav');
return (
<PopupWrapper />
);
}
const PopupWrapper = styled.div`
width: 400px;
position: relative;
display: none;
`;
i am not sure how to get the top-left corner of the leftnav and use it as coordinates to place the popup in center and 16px away to the leftnav.
could someone help me with this. thanks.
const App = () => {
function LeftNav() {
return (
<div
style={{
height: "50px",
background: "black",
width: "50px"
}}
id="left_nav"
/>
);
}
function PopUp() {
return (
<div
style={{
marginRight: "16px",
height: "100px",
background: "green",
width: "50px"
}}
className="popup"
/>
);
}
return (
<div
style={{ width: "100px", display: "flex", alignItems: "center"}}
className="App"
>
<LeftNav />
<PopUp />
</div>
);
}

Use Css modules add conditional transition styling to my side navigation

I would like to add some transition styling to my side navigation on my app. I am able to do this using normal classes however in this tutorial they use css modules and i am unsure how to do this using css modules.
I would like my nav to glide in and out, at the moment it jumps statically when the onClick function fires - toggleSideDrawer.
I have used this logic but I am not sure if it is doing anything:
className={props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
Essentially i want that when the user clicks the toggle, the transform property switches from translateX(-100%) to translateX(0) but this is not happening.
Side nav code:
import React from "react";
import Logo from "../../Logo/Logo";
import NavigationItems from "../NavigationItems/NavigationItems";
import Backdrop from "../../UI/Backdrop/Backdrop";
import Aux from "../../../hoc/Aux";
import classes from "./SideDrawer.css";
const SideDrawer = props => {
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={props.showSideDrawer}
clicked={props.toggleSideDrawer}
/>
{props.showSideDrawer && (
<div
onClick={props.toggleSideDrawer}
className={
props.toggleSideDrawer ? classes.SideDrawerOpen : classes.SideDrawer
}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
)}
</Aux>
);
};
export default SideDrawer;
Where the code is used in my Layout component:
import React, { useState } from "react";
import Aux from "../Aux";
import classes from "./Layout.css";
import Toolbar from "../../components/Navigation/Toolbar/Toolbar";
import SideDrawer from "../../components/Navigation/SideDrawer/SideDrawer";
const layout = props => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
return (
<Aux>
<SideDrawer
showSideDrawer={showSideDrawer}
toggleSideDrawer={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<Toolbar
onMenuClick={() => {
setShowSideDrawer(!showSideDrawer);
}}
/>
<main className={classes.mainContent}> {props.children} </main>
</Aux>
);
};
export default layout;
CSS:
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transform: translateX(-100%);
}
#media (min-width: 500px) {
.SideDrawer {
display: none;
}
}
.Logo {
height: 11%;
text-align: center;
}
.SideDrawerOpen {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
padding: 32px 16px;
box-sizing: border-box;
background-color: red;
transform: translateX(0);
transition: transform 0.3s ease-out;
}
The thing is that you need the element will has the transition rule all the time.
My suggestion is to set a static class which which will hold all the styles and addd another one only for overriding transform to make it move.
Something like that (it uses scss but it's easy to do it with css)
.SideDrawer {
position: fixed;
width: 280px;
max-width: 70%;
height: 100%;
left: 0;
top: 0;
z-index: 200;
background-color: white;
padding: 32px 16px;
box-sizing: border-box;
transition: transform .3s ease;
transform: translateX(-100%);
&.show {
transform: translateX(0);
}
}
export const App = () => {
const [showSideDrawer, setShowSideDrawer] = useState(false);
const sidebarClasses = classname([
styles.SideDrawer,
{
[styles.show]: showSideDrawer
}
]);
const ToggleSidebar = () => {
return (
<button onClick={() => setShowSideDrawer(!showSideDrawer)}>
Toggle Sidebar
</button>
);
};
return (
<Fragment>
<h1>App</h1>
<div className={sidebarClasses}>
<div>Sidebar content</div>
<ToggleSidebar />
</div>
<ToggleSidebar />
</Fragment>
);
};
https://codesandbox.io/s/xenodochial-framework-04sbe?file=/src/App.jsx
#MoshFeu helped me fix this.
The problem is that you render the drawer only when showSideDrawer so before it becomes true, the sidebar is not in the DOM yet so the transition is not affecting it.
The solution is to keep it in the DOM all the time but toggle . Open class to change the style.
There are libraries that knows to make the transition works even for elements that are not in the DOM but it's a bit more complicated.
code fix for SideDrawer.js without the conditional within the return
class SideDrawer extends Component {
render() {
let sideDrawerClass = [classes.SideDrawer];
// SideDrawer will now be an array with the side drawer classes and the open class
if (this.props.showSideDrawer) {
sideDrawerClass.push(classes.Open);
}
return (
<Aux classname={classes.SideDrawer}>
<Backdrop
showBackdrop={this.props.showSideDrawer}
clicked={this.props.toggleSideDrawer}
/>
<div
className={sideDrawerClass.join(" ")}
onClick={this.props.toggleSideDrawer}
>
<div className={classes.Logo}>
<Logo />
</div>
<nav>
<NavigationItems />
</nav>
</div>
</Aux>
);
}
}
export default SideDrawer;

Categories

Resources