Gsap scrolltrigger with timeline - reactjs

I'm making react app using gsap. I use scrolltrigger in timeline but it's not working. The scrolltrigger couldn't work.Can someone help me? Here is my code
gsap.registerPlugin(ScrollTrigger);
const el = useRef(null);
const q = gsap.utils.selector(el);
useEffect(() => {
let tl = gsap.timeline({
scrollTrigger: {
trigger: q(".scrollDist"),
start: "top top",
end: " bottom center",
scrub: 1,
markers: true,
},
});
tl.fromTo(
q(".header"),
{ y: 0, opacity: 1 },
{ y: -100, opacity: 0 }
).fromTo(".button_field", { y: 0 }, { y: -50 });
}, []);

A tad hard to pick up on what you're trying to achieve without a proper code snippet but I'll have a crack.
First glance it looks like you need to change the scrub: 1 config to scrub: true.
The other concern is it may not be querying for the elements properly, hard to tell without seeing the markup.
This is an assumption of the full code you have within your React component.
import React, { useEffect, useRef } from 'react'
import { gsap } from 'gsap'
import { ScrollTrigger } from 'gsap/all'
gsap.registerPlugin(ScrollTrigger);
const IndexPage = () => { {
const elementRef = useRef(null)
const q = gsap.utils.selector(elementRef)
useEffect(() => {
let tl = gsap.timeline(
{
scrollTrigger: {
trigger: q(".scrollDist"),
start: "top top",
end: "bottom center",
scrub: true, // scrub: 1
markers: true
}
}
);
tl
.fromTo(
q(".header"),
{ y: 0, opacity: 1 },
{ y: -100, opacity: 0 }
)
.fromTo(
".button_field",
{ y: 0 },
{ y: -50 }
);
}, [])
return (
<div ref={elementRef}>
<div className='scrollDist'></div>
<header className='header'>header</header>
<button className='button_field'>button</button>
</div>
)
}
export default IndexPage

Related

Is it possible to use threejs anaglyph Effect with highcharts3d in reactjs? If not any alternate method for Anaglyph effect in highcharts3d?

I have highcharts3d and I wanted to implement Anaglyph effect with highcharts3d
the below code is working component, You can copy paste this code into any of the reactJS application Only thing is not working in below example is anaglyph effect.
Expecting some suggestions or changes based on my current code
Thanks in advance.
import React, { useState, useEffect, useRef } from 'react';
import * as THREE from 'three';
import { AnaglyphEffect } from 'three/examples/jsm/effects/AnaglyphEffect.js'
import HighchartsReact from 'highcharts-react-official'
import Highcharts from 'highcharts'
import highcharts3d from 'highcharts/highcharts-3d'
import highchartsMore from 'highcharts/highcharts-more'
highcharts3d(Highcharts)
highchartsMore(Highcharts)
const HChart = () => {
const chartRef = useRef(null)
const [chartOptions] = useState({
chart: {
type: "column",
options3d: {
enabled: true,
},
events: {
render: (event) => {
event.target.reflow()
},
},
},
title: {
text: '3D Chart',
},
accessibility: {
enabled: false,
},
series: [
{
data: [],
color: 'rgba(0,255,255,0.5)'
},
]
})
useEffect(() => {
// const container = document.createElement('div');
const container = document.querySelector('.highcharts-container')
let camera, scene, renderer, effect;
init()
animate()
function init() {
camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.01,
100,
)
camera.position.z = 3
camera.focalLength = 3
scene = new THREE.Scene()
renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio(window.devicePixelRatio)
container.appendChild(renderer.domElement)
// chartRef.current.appendChild(renderer.domElement) // This line is giving error
const width = window.innerWidth || 2
const height = window.innerHeight || 2
effect = new AnaglyphEffect(renderer)
effect.setSize(width, height)
window.addEventListener('resize', onWindowResize)
}
function animate() {
requestAnimationFrame(animate)
render()
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight
camera.updateProjectionMatrix()
effect.setSize(window.innerWidth, window.innerHeight)
}
function render() {
effect.render(scene, camera)
}
}, [])
useEffect(() => {
const baseData = [49.9, 71.5, 106.4, 129.2, 144.0, 176.0, 135.6, 148.5, 216.4, 194.1, 95.6, 54.4];
const distance = 0.05;
const anaglyphicData = [];
baseData.forEach((dataEl, index) => {
anaglyphicData.push(
[index, dataEl], {
x: index - distance,
y: dataEl + distance,
color: 'rgba(255,0,0,0.5)'
}
);
});
chartRef.current.chart.series[0].setData(anaglyphicData)
},[])
function addEvents(H, chart) {
function dragStart(eStart) {
eStart = chart.pointer.normalize(eStart)
let posX = eStart.chartX,
posY = eStart.chartY,
alpha = chart.options.chart.options3d.alpha,
beta = chart.options.chart.options3d.beta,
sensitivity = 5, // lower is more sensitive
handlers = []
function drag(event) {
// Get e.chartX and e.chartY
event = chart.pointer.normalize(event)
chart.update(
{
chart: {
options3d: {
alpha: alpha + (event.chartY - posY) / sensitivity,
beta: beta + (posX - event.chartX) / sensitivity,
},
},
},
undefined,
undefined,
false,
)
}
function unbindAll() {
handlers.forEach(function(unbind) {
if (unbind) {
unbind()
}
})
handlers.length = 0
}
handlers.push(H.addEvent(document, 'mousemove', drag))
handlers.push(H.addEvent(document, 'touchmove', drag))
handlers.push(H.addEvent(document, 'mouseup', unbindAll))
handlers.push(H.addEvent(document, 'touchend', unbindAll))
}
H.addEvent(chart.container, 'mousedown', dragStart)
H.addEvent(chart.container, 'touchstart', dragStart)
// chartContainer = chart.container
}
return (
<div className="chartContainer" >
<HighchartsReact
highcharts={Highcharts}
ref={chartRef}
options={chartOptions}
allowChartUpdate={true}
containerProps={{ style: { width: '100%', height: '100%' } }}
callback={function(chart) {
addEvents(Highcharts, chart)
}}
/>
</div>
)
}
export default HChart;
There is not any integration between Highcharts and threejs. However, implementing an anaglyph effect seems to be really simple, for example by duplicating data.
For example:
const baseData = [1, 2, 3, 4, 5];
const distance = 0.05;
const anaglyphicData = [];
baseData.forEach((dataEl, index) => {
anaglyphicData.push(
[index, dataEl], {
x: index - distance,
y: dataEl + distance,
color: 'rgba(255,0,0,0.5)'
}
);
});
Highcharts.chart('container', {
...,
series: [{
data: anaglyphicData,
color: 'rgba(0,255,255,0.5)'
}]
});
Live demo: https://jsfiddle.net/BlackLabel/csrzj4d3/
Docs: https://www.highcharts.com/docs/chart-concepts/3d-charts

framer motion : different animation for mobile and desktop

Trying to make a different animation when on mobile or on desktop, to do so I'm using a useMediaQueryHoook and changing the variant in function of it. But the init animation seems to always assume that I'm on desktop. I guess because the useMediaQueryHook doesn't have time to actualise before the anim is launch. How can I deal with that issue ?
Btw I'm on nextjs :)
Here is my code :
const onMobile = useMediaQuery("(min-width : 428px)");
const wishCardVariant = {
hidden: (onMobile) => ({
opacity: 0,
y: onMobile ? "100%" : 0,
x: onMobile ? 0 : "100%",
transition,
}),
visible: (onMobile) => ({
opacity: 1,
x: 0,
y: 0,
}),
};
here is the hook :
import react, { useState, useEffect } from "react";
export default function useMediaQuery(query) {
const [matches, setMatches] = useState(false);
useEffect(() => {
const media = window.matchMedia(query);
if (media.matches !== matches) {
setMatches(media.matches);
}
const listener = () => {
setMatches(media.matches);
};
media.addListener(listener);
return () => media.removeListener(listener);
}, [matches, query]);
return matches;
}

Intersection Observer API with react

I am trying to create a navigation tracker on a page where a user scrolls with the intersection observer API and a tracker shows what section on the page in react like something shown in the website with this link https://olaolu.dev/ with the boxes that changes on scroll (scroll indicators), I already created a code sandbox. anyone who can assist me would mean a lot. been getting errors here and there don't even know what to do again, Link to code sandbox below
https://codesandbox.io/s/tender-oskar-rvbz5?file=/src/App.js
I only used react-intersection-observer with framer motion before, I added a ref to my h1 so the observer knows if it's inView or not.
controls.start will trigger if inView is true.
import { useInView } from "react-intersection-observer";
const scrollVariant = {
hidden: {
opacity: 0,
x: 0,
transition: {
duration: 1,
},
},
visible: {
opacity: 1,
x: 250,
transition: {
duration: 1,
},
},
};
export default function Home() {
const controls = useAnimation();
const { ref, inView } = useInView({
triggerOnce: false,
});
React.useEffect(() => {
if (inView) {
controls.start("visible");
}
if (!inView) {
controls.start("hidden");
}
}, [controls, inView]);
return (
<>
<motion.h1
variants={scrollVariant}
initial="hidden"
animate={controls}
ref={ref}
>
</>
);
}

Have TimelineLite only play once on Gatsby site

I have a TimelineLite timeline set up on my Gatsby site to animate my hero section when a user navigates to a page. However, if a user clicks a link to the current page i.e. if a user is on the homepage and clicks a link to the homepage, it is reloading the page and triggering the timeline to run again. Is there a way to make sure that my current link will be inactive within Gatsby?
Hero.tsx
import React, { useEffect, useRef, useState } from 'react';
import css from 'classnames';
import { ArrowButton } from 'components/arrow-button/ArrowButton';
import { HeadingReveal } from 'components/heading-reveal/HeadingReveal';
import { gsap, Power2, TimelineLite } from 'gsap';
import { RichText } from 'prismic-reactjs';
import htmlSerializer from 'utils/htmlSerializer';
import { linkResolver } from 'utils/linkResolver';
import s from './Hero.scss';
gsap.registerPlugin(TimelineLite, Power2);
export const Hero = ({ slice }: any) => {
const linkType = slice.primary.link._linkType;
const buttonLink =
linkType === 'Link.document' ? slice.primary.link._meta : slice.primary.link.url;
const theme = slice.primary.theme;
const image = slice.primary.image;
const contentRef = useRef(null);
const headingRef = useRef(null);
const copyRef = useRef(null);
const buttonRef = useRef(null);
const [tl] = useState(new TimelineLite({ delay: 0.5 }));
useEffect(() => {
tl.to(contentRef.current, { css: { visibility: 'visible' }, duration: 0 })
.from(headingRef.current, { y: 65, ease: Power2.easeOut, duration: 1 })
.from(copyRef.current, { opacity: 0, y: 20, ease: Power2.easeOut, duration: 1 }, 0.5)
.from(buttonRef.current, { opacity: 0, y: 10, ease: Power2.easeOut, duration: 1 }, 1);
}, [tl]);
return (
<div
className={css(s.hero, s[theme])}
style={{
background: image ? `url(${image.url})` : 'white',
}}
>
<div className={s.hero__container}>
<div className={s.content__left} ref={contentRef}>
<HeadingReveal tag="h1" headingRef={headingRef}>
{RichText.asText(slice.primary.heading)}
</HeadingReveal>
<div className={s.content__copy} ref={copyRef}>
{RichText.render(slice.primary.copy, linkResolver, htmlSerializer)}
</div>
<div className={s.content__button} ref={buttonRef}>
<ArrowButton href={buttonLink}>{slice.primary.button_label}</ArrowButton>
</div>
</div>
</div>
</div>
);
};
If you are using the built-in <Link> component to make that navigation that shouldn't happen since the #reach/router doesn't trigger and doesn't re-renders when navigating on the same page. There isn't any rehydration there.
If you are using an anchor (<a>) you are refreshing the full page so all your components will be triggered again.
In addition, another workaround that may do the trick in your case is to use a useEffect with empty deps ([]), creating a componentDidMount effect. In that case, since the page is not reloaded, it won't be triggered again:
const [tl] = useState(new TimelineLite({ delay: 0.5 }));
useEffect(() => {
tl.to(contentRef.current, { css: { visibility: 'visible' }, duration: 0 })
.from(headingRef.current, { y: 65, ease: Power2.easeOut, duration: 1 })
.from(copyRef.current, { opacity: 0, y: 20, ease: Power2.easeOut, duration: 1 }, 0.5)
.from(buttonRef.current, { opacity: 0, y: 10, ease: Power2.easeOut, duration: 1 }, 1);
return () => unmountFunction() // unmount to avoid infinite triggers
}, []);
Note: you may need to make another few adjustments to make the code work since I don't know your requirements. The idea is to remove the dependency of the t1 to bypass the issue.
To avoid infinite triggers you may also need to unmount the component with return () => unmountFunction().

Getting width of component in react

I'm trying to retrieve the width of my component using #rehooks/component-size which is meant to look something like this:
import { useRef } from 'react'
import useComponentSize from '#rehooks/component-size'
function MyComponent() {
let ref = useRef(null)
let { width } = useComponentSize(ref)
It worked for me before a refactor but now it just provides 0.
I'm trying to get the width of a linechart
<div className={styles.timeline}>
<Plot
{...baseChartSettings}
data={[{ x: time, y: groundSpeedData }]}
layout={{
xaxis: {
range: filters.byTime,
rangeslider: {
range: [0, data.maxTime],
},
},
}}
onUpdate={(
{
layout: {
xaxis: { range },
},
}: any // Figure, with 100% defined xaxis and yaxis atts
) => {
if (anyChangeInRange(filters.byTime, range)) {
filters.byTime = range;
setFilter({ ...filter });
}
}}
/>
</div>
edit: adding ref={ref} to the <div className={styles.timeline}> is what I was missing

Resources