React-Spring 3 useSpringValue(), useChain(), useTrail() indeterpolation and Parallax.
The useSpringValue() hook sets singular style properties, it accesses useSpring() methods imperatively.
//It doesn't require the API call
//it won't trigger on the updated component, we must use its methods.
let valore = useSpringValue(0, {
config: config.wobbly,
})
let valore1 = useSpringValue(0, {
config:{ mass: 20, friction: 3, tension: 10 },
})
function starto(){
valore.start(300)
valore1.start(300)
}
<div>
<animated.div className="barra1" style={{ width: valore }} >
</animated.div>
<animated.div className="barra2" style={{ width: valore1 }} >
</animated.div>
</div>

Animation sequences with useChain()
The useChain( [useSpringRef() array], [timesets], timeframe ) hook links multiple animations in a specified order, controlling their timing.
We useState() as a trigger for the useChain(), which will revert the animation's order and delays.
//Their timing delays will be timeset-array-value * timeframe (1000ms if not set)
const [open, set] = useState(false)
useChain(
open ? [colonna, refe] : [refe, colonna] ,
[0, open ? 1.5 : 3]
)
The useSpring() values and the useTransition() element depend on the useState(), to not trigger before the useChain() timing.
//Without a useChain() useSpringRef() would block the animation
const colonna = useSpringRef()
const { largo } = useSpring({
ref: colonna,
from: { largo: "0%" },
to: { largo: open ? "100%" : "0%" },
config: { duration: 1500 },
});
let lego = ["red", "blue", "green", "yellow" ]
let refe = useSpringRef();
let muro = useTransition( open ? lego : [], {
ref: refe,
trail: 2000 / lego.length,
from: {opacity: 0, height: 0 },
enter: {opacity: 1, height: 120 },
leave: {opacity: 0, height: 0},
config: {duration: 3000}
})
We change the order of the useTrasition() elements with its second property (item).
//The button is relative>absolute width animated background
<div className="d-block contenitore">
<div className="fila">
{muro((style, item) => (
<animated.div style={{ ...style, backgroundColor: item,
order: (lego.length - lego.indexOf( item )) }}>
</animated.div>
))
}
</div>
<div className='blocco' onClick={ ()=> set(!open) } >
<animated.div className="sfondo" style={{ width: largo }}></animated.div>
<div className="testo">Building</div>
</div>
</div>
We use display:grid on the useTransition() elements.
//We used a right movement to offset the grid-gap movement
.contenitore{
width: 60%;
height: 350px;
}
.fila {
position: relative;
display: grid;
grid-template-columns: repeat(4, minmax( 25%, 25% ));
grid-gap: 5px;
padding: 5px 10px 5px 10px;
right: 10px;
width: 60%;
margin: auto;
background: white;
}


Staggering animations with useTrail()
The useTrail() hook generates a trail of sequential animations.
We set the starting useTrail() properties and animate them on api methods (from/to would trigger the animation on start), each element's duration adds up to the previous ones.
//The integer sets the number of elements rendered
//We need the ()=> ({}) syntax, the 4th element takes 8 seconds to complete
const [ gira, setGira ] = useState( false )
let lista = [0, 50, 100, 150]
const [tasse, api] = useTrail( lista.length, ()=> ({
mosso: 0,
reverse: gira,
config: {duration: 1500}
}))
function nuota(){
(gira) ? api.start({ mosso: 0 }) : api.start({ mosso: 100 })
setGira((x)=> !x )
}
We need position absolute to avoid margins being influenced by the container area. We extract useTrail() style props and index to access the initial array.
//To string interpolate useTrail() values we need the to() method.
//the array integers being the hsl color we interpolate with mosso.prop
//marginLeft needed to not overlay the absolute elements
<div className="d-block position-relative">
{tasse.map(({mosso}, i) => (
<animated.div className="scatola position-absolute" onClick={ nuota }
style={{
marginLeft: ( 65*i ) +"px",
marginTop: mosso,
backgroundColor: mosso.to( val => `hsl( ${lista[i] + val}, 100%, 50% )` )
}}
>
{lista[i]}
</animated.div>
))}
</div>


Scroll sections with Parallax and ParallaxLayers.
We npm install @react-spring/parallax and extract the Parallax and ParallaxLayer.
A Parallax container animates its ParallaxLayer children onScroll() position. It comprises pages, each 100% height/width of the viewpoint, and fires its scroll events from the container, not the window.
import { Parallax, ParallaxLayer } from '@react-spring/parallax'
Parallax attributes:
page: value of the page total height of the container
config: to set tension, mass, friction
enabled/horizontal: true/false for vertical/horizontal scroll
innerStyle: CSS object for the inner Parallax object
ParallaxLayer attributes:
factor: Page scale of the parallaxLayer
offset: 0-index starting page position of the component
enabled/horizontal: by default inherited from the container
speed: for the rate of scroll
sticky: with start/end offset position of the sticky component
The Parallax page has to include all the different offset positions and factor parallaxLayers.
//Page is 1+ 0.8+ 1.5, the sticky layers are included in the offset:{1} page
<Parallax pages={3.3} className='meno' >
<ParallaxLayer offset={0}>
</ParallaxLayer>
<ParallaxLayer offset={1} sticky={{ start: 1, end: 1.8 }}>
</ParallaxLayer>
<ParallaxLayer offset={1.5}>
</ParallaxLayer>
<ParallaxLayer offset={1.8} factor={1.5}>
</ParallaxLayer>
</Parallax>
Using the offset position, the Parallax ref.current can trigger scrollTo() events. We remove the included scrollbar with a pseudo:selector and set left to fill the gap.
//ParallaxLayers have position-absolute and can overlay each other
.meno{
background: linear-gradient(lightblue, #9198e5);
left: 0px;
}
.meno::-webkit-scrollbar{
width: 0;
height: 0;
}
<Parallax pages={3.3} className='meno'>
<ParallaxLayer offset={0} className='d-flex justify-content-center'>
<div className="d-block w-25 mx-auto"> ... </div>
</ParallaxLayer>
<ParallaxLayer offset={1} sticky={{ start: 1, end: 1.8 }} className='d-flex'>
<div className="d-block w-100">
<div className="bg-warning d-flex" style={{ height: 55 }} >
</div>
<div className='w-25 ms-5 bg-danger mt-2'>
</div>
</div>
</ParallaxLayer>
<ParallaxLayer offset={1.5} className='d-flex justify-content-center'>
<div className="d-block w-100">
<div className='w-25 bg-success mt-2 me-5 ms-auto'> ... </div>
</div>
</ParallaxLayer>
<ParallaxLayer offset={1.8} factor={1.5} className='d-flex justify-content-end'>
<div className="d-block w-100">
<div className="bg-success d-flex justify-content-end" style={{ height: 55 }} >
</div>
<div className='w-25 me-5 bg-danger mt-2 ms-auto'>
</div>
</div>
</ParallaxLayer>
</Parallax>


Sticky ParallaxLayers, SVG images, and backgrounds
Don't use background on the sticky layer component, it will inherit the "container" layer.
It needs display: "inline-block" and height: 0, to not conflict with the other layer elements, its height is set only by its content.
We use vertical-align: "top" to align the inline-block elements to the sticky layer linebox, check CSS-1 for more vertical-align.
//You can't put a layer inside another layer, embed them using their offset position
//Sticky layers need only one container tag, its empty to not conflict with other
//Inline-block sticky content use vertical-align: baseline, top, middle, bottom.
//We justify sticky elements using width/margin for a 100% new line
<ParallaxLayer offset={1.5} style={{ height: 0, display: "inline-block"}}
sticky={{ start: 0.5, end: 1.5 }}>
<>
<div className="d-inline-block" style={{ verticalAlign: "top", width: "30%"}}>
<div style={{ backgroundColor: "brown", height: "65vh" }} >
This is the bar
</div>
</div>
<div className="d-inline-block" style={{ marginLeft: "20%",width: "40%" }}>
<div className="d-flex justify-content-between">
<h3> This is the other content </h3>
<div className="bg-warning">
<p> This is the second context </p>
</div>
</div>
</div>
</>
</ParallaxLayer>
Check the Intersection API section to see how Intersection Observer API interacts with <Parallax>
We import SVG icons in the ParallaxLayer, as image src or as a component, and modify their svg properties.
//To edit the fill we need to remove the default fill from the svg file
//SVG proportions are independent of height/width, they can be backgroundImage.
import planet from "../images/planet.svg"
import { ReactComponent as Mac } from "../images/car.svg"
<ParallaxLayer offset={0.5} className='d-flex justify--center align-items-center'>
<img src={planet} style={{ width: "50%" }} />
</ParallaxLayer>
<ParallaxLayer offset={1.55} >
<Mac style={{ fill:"blue", marginLeft: "32%", width: "20%", height: "10%" }}/>
</ParallaxLayer>
The parallaxLayer's order is backgroundColor > SVG icons > SVG backgroundImage. We scrollTo() an entire Page by including specific offset ranges in a <div> container.
More images in the same ParallaxLayer won't overlay and will sum their height, z-index works between layers and can override the overflow:hidden.

Last updated
Was this helpful?