GSAP Scrolltrigger PIN not working properly in nextjs - css

From my code below, If I comment out the pin: true property, the code works normally but the container that wraps the sections I expect to scroll horizontally are not sticky to the top. If I uncomment the pin: true, all the container (trigger) will not be visible.
Any suggestions on how to resolve this issue will be greatly appreciated.
import React, { useEffect } from "react";
import OverlayMenu from "./OverlayMenu";
import { gsap } from "gsap";
import ScrollTrigger from "gsap/dist/ScrollTrigger";
function MainContent({ overlayRef }) {
gsap.registerPlugin(ScrollTrigger);
useEffect(() => {
// alert(document.querySelector(".main__content").offsetWidth)
const sections = gsap.utils.toArray(".section");
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: "none",
scrollTrigger: {
trigger: ".main__content",
scrub: 1,
markers: true,
start: "top top",
// // snap: 1 / (sections.length - 1),
end: "+=" + document.querySelector(".main__content").offsetWidth,
pin: true,
},
});
}, []);
return (
<div className="main__content__wrapper w-[calc(100%_-_80px)] h-screen ml-20">
<div className="w-full relative h-screen">
<OverlayMenu overlayRef={overlayRef} />
{/* <div className="w-full h-screen bg-black"></div> */}
<div className="main__content w-[300%] bg-purple-700 h-screen flex flex-nowrap">
<div className="section w-full h-screen- bg-red-500">1</div>
<div className="section w-full h-screen- bg-blue-500">2</div>
<div className="section w-full h-screen- bg-yellow-500">3</div>
</div>
</div>
</div>
);
}
export default MainContent;

Step back the version to 3.8.0
yarn add gsap#3.8.0
OR
npm install gsap#3.8.0
You can read more here:
https://greensock.com/forums/topic/30747-scrolltrigger-in-nextjs-build-not-working-reliably/#comment-153675

import React, { useEffect, useState } from "react";
import OverlayMenu from "./OverlayMenu";
import { gsap } from "gsap";
import ScrollTrigger from "gsap/dist/ScrollTrigger";
function MainContent({ overlayRef }) {
gsap.registerPlugin(ScrollTrigger);
const [hasRendered, setHasRendered] = useState(false);
useEffect(() => {
// ******This will set the state of hasRendered when the page render so as to keep track of the rendering steps *******
setHasRendered(true);
}, []);
useEffect(() => {
// ******* I added the conditional statement here to only create the instance of the gsap timeline ONLY after the page has rendered*******
if (hasRendered) {
// alert(document.querySelector(".main__content").offsetWidth)
const sections = gsap.utils.toArray(".section");
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: "none",
scrollTrigger: {
trigger: ".main__content",
scrub: 1,
markers: true,
start: "top top",
// // snap: 1 / (sections.length - 1),
end: "+=" + document.querySelector(".main__content").offsetWidth,
pin: true,
},
});
}
}, [hasRendered]); //******** Dont forget the dependencies array too */
return (
<div className="main__content__wrapper w-[calc(100%_-_80px)] h-screen ml-20">
<div className="w-full relative h-screen">
<OverlayMenu overlayRef={overlayRef} />
{/* <div className="w-full h-screen bg-black"></div> */}
<div className="main__content w-[300%] bg-purple-700 h-screen flex flex-nowrap">
<div className="section w-full h-screen- bg-red-500">1</div>
<div className="section w-full h-screen- bg-blue-500">2</div>
<div className="section w-full h-screen- bg-yellow-500">3</div>
</div>
</div>
</div>
);
}
export default MainContent;

Related

Outlet component shifts my footer from visible page | React-router-dom with Tailwind CSS

Please help me to solve my problem.
When I use
h-screen
to every element in Output, it makes scrolls on a single page which I don't need. My footer is not on the bottom of visible page, it's under Outler. How to make an Outlet not to shift my footer outside the visible page?
this is my main.jsx
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, Outlet, RouterProvider } from 'react-router-dom';
import Index from './pages/Index';
import './index.css';
import ErrorPage from './pages/ErrorPage';
import Personnel from './pages/Personnel';
import NewNavbar from './components/NewNavbar';
import Footer from './components/Footer';
import Contacts from './pages/Contacts';
import About from './pages/About';
const AppLayout = () => {
return (
<div className=' flex justify-between flex-col bg-gradient-to-r from-zinc-900 via-zinc-700 to-zinc-900'>
<NewNavbar />
<Outlet />
<Footer />
</div>
);
};
const router = createBrowserRouter([
{
element: <AppLayout />,
path: '/',
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Index />,
},
{
path: '/personnel',
element: <Personnel />,
},
{
path: '/contacts',
element: <Contacts />,
},
{
path: '/about',
element: <About />,
},
],
},
]);
ReactDOM.createRoot(document.getElementById('root')).render(
<React.StrictMode>
<RouterProvider router={router} />
</React.StrictMode>
);
Index, About and Contacts are similar
import React from 'react';
const Index = () => {
return (
<main className='bg-red-500 '>This is main page
</main>
);
};
export default Index;
As I said before Personnel.jsx works as I expected, but not sure that is right.
import React from 'react';
import Administration from '../components/Administration';
import Teachers from '../components/Teachers';
const Personnel = () => {
return (
<div className='flex w-full flex-col'>
<div className='w-full px-[8%] lg:mx-auto lg:max-w-7xl text-white '>
<Administration />
<Teachers />
</div>
</div>
);
};
export default Personnel;
Each of these two elements has:
<div className='flex min-h-screen flex-col justify-center'>
h-screen will set an element with height: 100vh, meaning it will take the span of the entire viewport.
If I understand your problem correctly, you should set your AppLayout component with min-h-screen, so all the components inside it (NewNavbar, Outlet and Footer) will take at least the height of the viewport:
const AppLayout = () => {
return (
<div
className='min-h-screen flex justify-between flex-col bg-gradient-to-r from-zinc-900 via-zinc-700 to-zinc-900'
>
<NewNavbar />
<Outlet />
<Footer />
</div>
);
};

React-hydration-error every time when I put motion from framer-motion

I am facing a hydration error when trying to add motion from framer-motion in any element from the file.
Without motion, everything works fine:
import { useRef, useState, useEffect } from 'react';
import { usePathname, useRouter } from 'next/navigation';
import Image from 'next/image';
import Link from 'next/link';
import { Icon, Dropdown, UserIcon } from '#ui';
import { menuItems } from '#utils/menu';
import useOutsideClick from '#hooks/useOutsideClick';
import { useAppDispatch, useAppSelector } from '#hooks/reduxHooks';
import { triggerCloseMenu } from 'redux/Slices/menuSlice';
import { motion } from 'framer-motion';
const Header = () => {
const [url, setUrl] = useState<string>('/images/logoLight.png');
const dispatch = useAppDispatch();
const close = () => {
dispatch(triggerCloseMenu());
};
const menuOpen = useAppSelector((state) => state.menu.menuOpen);
const wrapperRef = useRef(null);
const pathname = usePathname();
const router = useRouter();
useOutsideClick(wrapperRef, close);
useEffect(() => {
setUrl(
pathname === '/'
? !menuOpen
? '/images/logoLight.png'
: '/images/logoDark.png'
: '/images/logoDark.png',
);
}, [menuOpen, pathname]);
return (
<header
className="fixed top-0 z-50 w-screen bg-transparent "
ref={wrapperRef}
>
<nav className=" mx-auto flex w-full max-w-[2000px] items-center justify-between py-9 px-7 ">
<div className="flex items-center justify-center space-x-6">
<Icon />
<Image
src={url}
alt="logo_image"
width={120}
height={0}
className="mb-1"
onClick={() => {
router.push('/');
close();
}}
/>
</div>
<div className="relative flex items-center space-x-10">
<div className="hidden space-x-14 lg:flex">
{menuItems.map((i: Menu) => (
<Link
href={i.path}
key={i.id}
className={`${
pathname !== i.path ? 'font-normal' : 'font-extrabold'
}
text-lg text-white transition duration-300 ease-linear`}
>
{i.label}
</Link>
))}
</div>
<UserIcon />
</div>
</nav>
<Dropdown />
</header>
);
};
export default Header;
But if I replace with <motion.header> </motion.header> I am getting this error : Error: Hydration failed because the initial UI does not match what was rendered on the server.
I am using latest version of Nextjs (without app directory), and framer-motion v8

CSS : Text is overflowing from div

I'm using tailwind with react. The problem is that my paragraph text is quite long. I have applied overflow-hidden to the div. But it is still flowing outside div. But it is not visible. But it is ruining my UI. As the pic is also getting stretched out due to the text overflowing. How do I stop the text from overflowing. So I only get the text that is visible inside div. I am using subString(0,50). It will fix the issue, if I use a smaller number. But I want to know is there any other way to fix it. So that the text does not go outside the div and occupies full space of div only. I tried many other properties to fix this issue. But no success so far.
I have uploaded a gif of my problem on imgur
Code
import React, { useEffect, useState } from "react";
import { BsBoxArrowUpRight, BsPencilSquare, BsTrash } from "react-icons/bs";
import { getActors } from "../api/actor";
let currentPageNo = 0;
let limit = 20;
export default function Actors() {
const [actors, setActors] = useState([]);
const [reachedToEnd, setReachedToEnd] = useState(false);
const fetchActors = async (pageNo) => {
const { profiles, error } = await getActors(pageNo, limit);
if (!profiles.length) {
currentPageNo = pageNo - 1;
return setReachedToEnd(true);
}
setActors([...profiles]);
};
const handleOnNextClick = () => {
if (reachedToEnd) return;
currentPageNo += 1;
fetchActors(currentPageNo);
};
const handleOnPrevClick = () => {
if (currentPageNo <= 0) return;
currentPageNo -= 1;
fetchActors(currentPageNo);
};
useEffect(() => {
fetchActors(currentPageNo);
}, []);
return (
<div className="p-5">
<div className="grid grid-cols-4 gap-5">
{actors.map((actor) => {
return <ActorProfile profile={actor} key={actor.id} />;
})}
</div>
<div className="flex justify-end items-center space-x-3 mt-5">
<button
type="button"
className="text-primary dark:text-white hover:underline"
onClick={handleOnPrevClick}
>
Prev
</button>
<button
type="button"
className="text-primary dark:text-white hover:underline"
onClick={handleOnNextClick}
>
Next
</button>
</div>
</div>
);
}
const ActorProfile = ({ profile }) => {
if (!profile) return null;
const [showOptions, setShowOptions] = useState(false);
const handleOnMouseEnter = () => {
setShowOptions(true);
};
const handleOnMouseLeave = () => {
setShowOptions(false);
};
let acceptedNameLength = 15;
const getName = (name) => {
if (name.length <= acceptedNameLength) return name;
return name.substring(0, acceptedNameLength) + "...";
};
const { name, avatar, about = "" } = profile;
return (
<div className="bg-white dark:bg-secondary shadow dark:shadow rounded h-20 overflow-hidden">
<div
onMouseEnter={handleOnMouseEnter}
onMouseLeave={handleOnMouseLeave}
className="flex cursor-pointer relative"
>
<img
src={avatar}
alt={name}
className="w-20 aspect-square object-cover"
/>
<div className="px-2 flex-1">
<h1 className="text-xl text-primary dark:text-white whitespace-nowrap">
{getName(name)}
</h1>
<p className="text-primary dark:text-white opacity-75">
{about.substring(0, 30)}
</p>
</div>
<Options visible={showOptions} />
</div>
</div>
);
};
const Options = ({ visible, onEditClick, onDeleteClick }) => {
if (!visible) return null;
return (
<div className="absolute inset-0 bg-primary bg-opacity-25 backdrop-blur-sm flex justify-center items-center space-x-5">
{" "}
<button
onClick={onDeleteClick}
type="button"
className="text-primary bg-white p-2 rounded-full hover:opacity-80 transition"
>
<BsTrash />
</button>
<button
onClick={onEditClick}
type="button"
className="text-primary p-2 rounded-full bg-white hover:opacity-80 transition"
>
<BsPencilSquare />
</button>
</div>
);
};

How can fix column property on the next fetch?

This is my first time I am asking question here. If I make any mistake, sorry from now on. I've been trying to build infinite scroll bar with 3 columns. But when the next data is fetched, the images act really weird. I've tried almost everything like "fixed, flex-nowrap", etc.. with tailwind. But none of them worked. I also made research almost for 3 hours and couldn't find any helpful resources. I'd be glad if you could help me!
video: https://streamable.com/ouk16y
import React, { useState, useEffect } from "react";
import InfiniteScroll from "react-infinite-scroll-component";
//components
//icons
import { AiOutlineHeart, AiOutlinePlus } from "react-icons/ai";
//styling
const Gallery = () => {
const apiKey = "apiKey";
const [pictures, setPictures] = useState([]);
const [page, setPage] = useState(1);
//fetching the api data
useEffect(() => {
fetch(
`https://api.unsplash.com/search/photos?page=${page}&query=office&client_id=${apiKey}`
)
.then((resp) => {
return resp.json();
})
.then((data) => {
const pictureData = [...new Set(data.results)];
setPictures((prev) => [...prev, ...pictureData]);
});
}, [page]);
return (
<InfiniteScroll
dataLength={pictures.length}
next={() => setPage((prev) => prev + 1)}
hasMore={true}
scrollThreshold={0.3}
>
<div className="columns-1 lg:columns-3 col-auto lg:w-5/6 mx-auto gap-8 space-y-4">
{pictures.map((picture) => (
<div className="" key={picture.id}>
<div className="flex p-2 lg:hidden ">
<img
className="rounded-full w-10 h-10"
src={picture.user.profile_image.medium}
alt={`${picture.name} profile`}
/>
<span className="pt-2 pl-2">{picture.user.name}</span>
</div>
<img className="resize-none" src={picture.urls.regular} />
{/* //icons for small devices// */}
<div className="flex mt-2 pl-2 mb-8 lg:hidden">
<AiOutlineHeart className="w-8 h-8 text-gray-600" />
<AiOutlinePlus className="w-8 h-8 ml-2 text-gray-600 " />
<button className="w-40 border ml-auto mr-4">Download</button>
</div>
</div>
))}
</div>
</InfiniteScroll>
);
};
export default Gallery;
'''

How do i make Tailwind.css responsive?

I have two components.In first MovieItem.js i created a card.In second Movies.js i am looping through an array of cards and displaying them in UI - 4 per row.I am using Tailwind grid template columns to do this, and it s working fine.
return (
<div className="grid grid-cols-4 gap-4">
{movies.map((movie) => (
<MovieItem key={movie.id} movie={movie}></MovieItem>
))}
</div>
);
However if the width of the screen is less then 640 pixels i want to show only one card per row which means that each card should have width 100% and that they should be all horizontally stacked on top of each other, but i am not getting any results with this.
return (
<div className="grid grid-cols-4 sm:grid grid-cols-1 gap-4">
{movies.map((movie) => (
<MovieItem key={movie.id} movie={movie}></MovieItem>
))}
</div>
);
MovieItem.js
import React from "react";
import PropTypes from "prop-types";
import { Link } from "react-router-dom";
import AlternativeImage from "./AlternativeImage";
const MovieItem = ({ movie: { id, title, poster_path } }) => {
return (
<div className=" bg-gray-900 px-4 pt-4 pb-5 overflow-hidden rounded ">
{poster_path ? (
<img
src={"https://image.tmdb.org/t/p/w400/" + poster_path}
alt=""
className="w-full block content-center p-4 content-image"
/>
) : (
<AlternativeImage></AlternativeImage>
)}
<h6 className="text-base text-center text-white my-3 font-mono">
{title}
</h6>
<div className="button-container">
<Link
to={`/movie/${id}`}
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded "
>
Movie Details
</Link>
</div>
</div>
);
};
export default MovieItem;
Movies.js
import React, { useContext } from "react";
import MovieItem from "./MovieItem";
import MovieContext from "../context/movie/movieContext";
const Movies = () => {
const movieContext = useContext(MovieContext);
const { movies } = movieContext;
return (
<div className="grid grid-cols-4 sm:grid grid-cols-1 gap-4">
{movies.map((movie) => (
<MovieItem key={movie.id} movie={movie}></MovieItem>
))}
</div>
);
};
export default Movies;
I also made necessary changes in Tailwind config file:
const tailwindcss = require("tailwindcss");
module.exports = {
theme: {
screens: {
xl: { max: "1279px" },
// => #media (max-width: 1279px) { ... }
lg: { max: "1023px" },
// => #media (max-width: 1023px) { ... }
md: { max: "767px" },
// => #media (max-width: 767px) { ... }
sm: { max: "639px" },
// => #media (max-width: 639px) { ... }
},
},
plugins: [tailwindcss("./tailwind.js"), require("autoprefixer")],
};
By default, Tailwind uses a mobile first breakpoint system, similar to what you might be used to in Bootstrap or Foundation.
What this means is that unprefixed utilities (like uppercase) take effect on all screen sizes, while prefixed utilities (like md:uppercase) only take effect at the specified breakpoint and above.
In your case, simply do this
grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-2 md:gap-4
Check docs.

Resources