I'm trying to make it so that a border transitions on to the page smoothly once I reach a y point but I am having trouble with the transition animation. I'm using react and tailwind.
This is the code I have so far.
const Navbar = () => {
const [navStyles, setNavStyles] = useState(false);
useEffect(() => {
const handleNavStyles = () => {
if (window.scrollY > 80) {
setNavStyles(true);
} else {
setNavStyles(false);
}
};
window.addEventListener('scroll', handleNavStyles);
}, []);
return (
<header className="sticky top-0 z-10 backdrop-blur-md ">
<nav
className={`mx-auto flex max-w-screen-sm items-center space-x-3 py-3 px-4 sm:py-5 sm:px-0 ${
navStyles ? 'border-b transition duration-300 ease-in' : ''
}`}
>
<div>Navbar</div>
</nav>
</header>
);
};
Use transition-all instead of transition.
Related
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
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;
I have added carousel functionality but the images are flickering whenever I click on next icon. It seems like I get the reflection of the previous image or the next one as I have only two images. I'm using tailwind with react. I'm adding a gif below so one can see the problem. I'm getting flickering every time. But my screen recorder shows it only the first time.
Link to Gif
Index.css
.slide-in-from-right {
animation: slideInFromRight 0.5s ease-in;
}
.slide-out-to-left {
animation: slideOutToLeft 0.5s ease-in;
}
#keyframes slideInFromRight {
from {
transform: translateX(100%);
}
to {
transform: translateX(0%);
}
}
#keyframes slideOutToLeft {
from {
transform: translateX(0%);
}
to {
transform: translateX(-100%);
}
}
Hero SlideShow
let count = 0;
export default function HeroSlideshow() {
const [slide, setSlide] = useState({});
const [clonedSlide, setClonedSlide] = useState({});
const [slides, setSlides] = useState([]);
const [currentIndex, setCurrentIndex] = useState(0);
const slideRef = useRef();
const clonedSlideRef = useRef();
const { updateNotification } = useNotification();
const fetchLatestUploads = async () => {
const { error, movies } = await getLatestUploads();
if (error) return updateNotification("error", error);
setSlides([...movies]);
setSlide(movies[0]);
};
//0,1,2,3,4
const handleOnNextClick = () => {
setClonedSlide(slides[count]);
clonedSlideRef.current.classList.add("slide-out-to-left");
count = (count + 1) % slides.length;
setSlide(slides[count]);
setCurrentIndex(count);
slideRef.current.classList.add("slide-in-from-right");
};
const handleAnimationEnd = () => {
slideRef.current.classList.remove("slide-in-from-right");
clonedSlideRef.current.classList.remove("slide-out-to-left");
setClonedSlide({});
};
useEffect(() => {
fetchLatestUploads();
}, []);
return (
<div className="w-full flex">
{/* Slide show section */}
<div className="w-4/5 aspect-video relative overflow-hidden">
<img
ref={slideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover"
src={slide.poster}
alt=""
/>
<img
ref={clonedSlideRef}
onAnimationEnd={handleAnimationEnd}
className="aspect-video object-cover absolute inset-0"
src={clonedSlide.poster}
alt=""
/>
<SlideShowController onNextClick={handleOnNextClick} />
</div>
{/* Up Next Section */}
<div className="w-1/5 aspect-video bg-red-300"></div>
</div>
);
}
const SlideShowController = ({ onNextClick, onPrevClick }) => {
const btnClass =
"bg-primary rounded border-2 text-white text-xl p-2 outline-none";
return (
<div className="absolute top-1/2 -translate-y-1/2 w-full flex items-center justify-between px-2">
<button onClick={onPrevClick} className={btnClass} type="button">
<AiOutlineDoubleLeft />
</button>
<button onClick={onNextClick} className={btnClass} type="button">
<AiOutlineDoubleRight />
</button>
</div>
);
};
It's difficult to answer if I cannot reproduce the issue but I have a clue of what's going on. When you call the method to handleOnNextClick you must take on mind that the slide's index (which is actually showing it as selected)'ll change almost instantly but the animation slideInFromRight and slideOutToLeft is taking .5 seconds to complete, so you can try to trigger the slide's index change with a setTimeout.
const handleOnNextClick = () => {
clonedSlideRef.current.classList.add("slide-out-to-left");
slideRef.current.classList.add("slide-in-from-right");
count = (count + 1) % slides.length;
setTimeout(() => {
setClonedSlide(slides[count]);
setSlide(slides[count]);
setCurrentIndex(count);
}, 500)
};
Hope it helps!
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>
);
};
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;
'''