Set navbar fixed on scroll with transition in react.js - reactjs

I set my navbar fixed on scroll but I want it to reappear with transition instead of bumping right into the screen. I have tried many solutions but the transition effect won't kick in.
I have the following react code:
const [sticky, setSticky] = useState('');
React.useEffect(() => {
window.addEventListener('scroll', stickNavbar);
return () => window.removeEventListener('scroll', stickNavbar);
}, []);
const stickNavbar = () => {
if (window !== undefined) {
let windowHeight = window.scrollY;
windowHeight > 150 ? setSticky({ position: "fixed", top: "0"}) : setSticky({ position: "relative"});
}
};
return(
<Navbar>
<Container style={sticky} fluid className="menu">
</Container>
</Navbar>
)
CSS
.menu {
max-width: 100%;
height: 7rem;
border-bottom: 2px solid rgb(228, 228, 228);
background-color:#f8f9fa;
transition: top 1s;
}

The postition property has to change in order for the effect to kick in. Therefore I added negative values for the top attribute and the same positive amount for margin. To cancel the effect for backward transition I set the css value to 0s.
const stickNavbar = () => {
if (window !== undefined) {
let windowHeight = window.scrollY;
windowHeight > 150 ? setSticky({ position: "fixed", top: "0", marginTop:"0",
transition: "top 1s"}) : setSticky({});
}
};
.menu {
max-width: 100%;
height: 7rem;
border-bottom: 2px solid rgb(228, 228, 228);
background-color:#f8f9fa;
position: relative;
top:-30px;
margin-top: 30px;
transition: top 0s;
z-index: 99999;
}

Related

How do I dynamically add a div where mouse was clicked in React?

I'm new to react and wonder how to do weird code stuff. I have a div component that I need to add child divs to depending on where I clicked on the div. I could do this easily in vanilla JS - here is a code sandbox of JS of what I want to do : https://codepen.io/Webasics/pen/YXXyEO
here is what I have in react so far (this is inside my App component):
const imgAdder = (e) => {
console.log(e.pageX, e.pageY)
}
<main onClick={imgAdder} </main>
$(document).ready(function() {
$(this).click(function(e) {
var x = e.pageX;
var y = e.pageY;
$('<div/>').css({
'top': y,
'left': x
}).appendTo('body');
});
});
div {
background-color: red;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
/* optional */
border: 1px solid black;
/* optional */
}
h2 {
z-index: 10;
/* optional */
/* This always keeps the title on top*/
position: absolute;
}
body {
background-color: #E1E7E8;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<h2>Click anywhere</h2>
Any directions would be lovely ! thank you.
function App() {
// declare array of boxes
const [boxes, setBoxes] = useState([]);
const handleClick = ({ pageX, pageY }) => {
// on every click push a new coordinate to the boxes array
setBoxes((boxes) => [...boxes, { x: pageX, y: pageY }]);
};
return (
<div className="app" onClick={handleClick}>
// display boxes
{boxes.map((box) => (
// map coordinates to left and top
<div className="box" style={{ left: box.x, top: box.y }}></div>
))}
</div>
);
}
Styles, mostly copied from the codepen
.app {
width: 100%;
height: 100vh;
}
.box {
position: absolute;
width: 50px;
height: 50px;
background: red;
transform: translate(-50%, -50%);
}
sandbox
Weird, but I like it!
https://codesandbox.io/s/elated-meadow-zuerrg?file=/src/App.js
I would simply use useEffect to register a click handler on the document and on click, add elements to a state array.
Finally, render those elements onto the page.
import { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const elements = useDynamicElements();
return (
<>
<h2>Click anywhere</h2>
{elements}
</>
);
}
const useDynamicElements = () => {
const [state, setState] = useState([]);
useEffect(() => {
const handler = (event) => {
setState((previous) => [
...previous,
<div style={{ top: event.pageY, left: event.pageX }} />
]);
};
document.addEventListener("click", handler);
return () => document.removeEventListener("click", handler);
});
return state;
};
An over simplified example in React could be like this:
This version can run in the snippets below for convenience.
const App = () => {
const [boxList, setBoxList] = React.useState([]);
const handleClick = (e) => {
if (e.target.classList.contains("btn")) {
setBoxList([]);
return;
}
setBoxList((prev) => {
const { pageX, pageY } = e;
const newBox = { left: pageX, top: pageY };
return [...prev, newBox];
});
};
return (
<div className="app" onClick={handleClick}>
<button className="btn">CLEAN UP</button>
<h2>Click anywhere</h2>
{boxList.length > 0 &&
boxList.map((box, index) => (
<div className="box" style={{ top: box.top, left: box.left }} key={index}></div>
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#root"));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.app {
width: 100%;
height: 100vh;
background-color: pink;
position: relative;
}
.box {
background-color: #000;
width: 50px;
height: 50px;
position: absolute;
transform: translate(-50%, -50%);
border: 1px solid black;
}
h2 {
top: 50%;
left: 50%;
position: absolute;
transform: translate(-50%, -50%);
}
.btn {
margin: 15px;
padding: 15px;
background-color: #fff;
border: 0;
border-radius: 12px;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.1.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.1.0/umd/react-dom.production.min.js"></script>

Mapped buttons that have opacity of 0 are showing, and are overlapping when resizing the window

I've successfully mapped over text and have styled it with transition effects when going from one slide to the next as you can see here:
Following a similar concept with buttons isn't working. There should only be one button active per slide like you see here:
I want the buttons to have the same effect as the text, but I'm getting behaviors like you see here:
As you can see, there is no transition effect on the button when clicking to the second slide, and it also appears in a lower spot.
And lastly, when resizing the window, buttons are overlapping like you see here:
Don't know what to try next.
Here's the ImageSlider component:
import { useState } from "react";
import { SliderData } from "../data";
import { categories } from "../data";
import ShopNowButtonActive from "./ShopNowButtonActive";
import { IoIosArrowBack } from "react-icons/io";
import { IoIosArrowForward } from "react-icons/io";
import "./ImageSlider.css";
import ShopNowButton from "./ShopNowButton";
const ImageSlider = ({ slides }) => {
const [current, setCurrent] = useState(0);
const length = slides.length;
const nextSlide = () => {
setCurrent(current === length - 1 ? 0 : current + 1);
};
const prevSlide = () => {
setCurrent(current === 0 ? length - 1 : current - 1);
};
return (
<div className="slider">
<IoIosArrowBack className="left-arrow" onClick={prevSlide} />
{SliderData.map((slide, index) => (
<div key={slide.id}>
<img
src={slide.img}
alt=""
className={index === current ? "slide active" : "slide"}
/>
<div className="info-container">
<div className={index === current ? "title active" : "title"}>
{slide.title}
</div>
<div className={index === current ? "desc active" : "desc"}>
{slide.desc}
</div>
{categories.map((item, index) =>
index === current ? (
<ShopNowButtonActive item={item} />
) : (
<ShopNowButton item={item} />
)
)}
</div>
</div>
))}
<IoIosArrowForward className="right-arrow" onClick={nextSlide} />
</div>
);
};
export default ImageSlider;
The css file:
.slider {
height: 90vh;
margin-bottom: 0.5rem;
}
.left-arrow {
position: absolute;
top: 45%;
left: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
.slide.active {
opacity: 1;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.slide {
opacity: 0;
transition: 500ms opacity ease-in-out;
width: 100%;
height: 88%;
object-fit: cover;
object-position: center;
position: absolute;
-webkit-clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
clip-path: polygon(100% 0, 100% 80%, 50% 100%, 0 80%, 0% 0%);
}
.info-container {
display: flex;
flex-direction: column;
width: 40%;
height: 100%;
position: absolute;
top: 45%;
right: 30px;
}
.title.active {
opacity: 1;
transition-delay: 700ms;
font-size: 4rem;
}
.title {
opacity: 0;
transition: 200ms opacity ease-in-out;
font-size: 4rem;
}
.desc.active {
opacity: 1;
padding-top: 1.5em;
transition-delay: 700ms;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.desc {
opacity: 0;
padding-top: 1.5em;
transition: 200ms opacity ease-in-out;
font-size: 1.25rem;
font-weight: 500;
letter-spacing: 3px;
}
.right-arrow {
position: absolute;
top: 45%;
right: 32px;
font-size: 2rem;
cursor: pointer;
opacity: 0.5;
z-index: 1;
}
The ShopNowButtonActive component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const ButtonActive = styled.button`
opacity: 1;
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition-delay: 700ms;
`;
const ShopNowButtonActive = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<ButtonActive>SHOP NOW</ButtonActive>
</Link>
);
};
export default ShopNowButtonActive;
And finally, the ShopNowButton component:
import React from "react";
import styled from "styled-components/macro";
import { Link } from "react-router-dom";
const Button = styled.button`
opacity: 0;
/* display: none; */
padding: 0.5rem;
margin-top: 2.5rem;
width: 8rem;
font-size: 20px;
background-color: transparent;
cursor: pointer;
transition: 200ms opacity ease-in-out;
`;
const ShopNowButton = ({ item }) => {
return (
<Link to={`/products/${item.cat}`}>
<Button>SHOP NOW</Button>
</Link>
);
};
export default ShopNowButton;
(Sorry for the use of both an external css file and styled components.)
Any suggestions?
I have recreated the above scenario using some static data. I have modified some of the css. It is working as expected. I also observed that the only major difference between ShopNowButton and ShopNowButtonActive was opacity property which hides the element. The reason you are observing 2 buttons because they were all there in the dom actually.(They were not hiding properly due to which we are observing more shop now buttons and every time we click on next icon the corresponding button is being displayed. Basically all the buttons are there on the page itself.)
Please find the sandbox url below.
https://codesandbox.io/s/stackoverflow-6u05e7?file=/src/ImageSlider/ImageSlider.js

Position Draft js emoji popover on top

I'm using the #draft-js-plugins/emoji but I'm having problem positioning the emoji popover on top of the editor. Currently it's showing on bottom like this I want to show this popover on top rather than the bottom.
I found an answer here, but it's not working for me.
const {
MentionSuggestions,
EmojiSuggestions,
EmojiSelect,
plugins,
} = useMemo(() => {
const mentionPlugin = createMentionPlugin();
// eslint-disable-next-line no-shadow
const { MentionSuggestions } = mentionPlugin;
const emojiPlugin = createEmojiPlugin({
positionSuggestions: (settings) => {
return {
left: settings.decoratorRect.left + 'px',
top: settings.decoratorRect.top - 40 + 'px', // change this value (40) for manage the distance between cursor and bottom edge of popover
display: 'block',
transform: 'scale(1) translateY(-100%)', // transition popover on the value of its height
transformOrigin: '1em 0% 0px',
transition: 'all 0.25s cubic-bezier(0.3, 1.2, 0.2, 1)',
position: 'fixed',
};
},
useNativeArt: true,
});
// eslint-disable-next-line no-shadow
const { EmojiSuggestions, EmojiSelect } = emojiPlugin;
// eslint-disable-next-line no-shadow
const plugins = [mentionPlugin, emojiPlugin];
return {
plugins,
MentionSuggestions,
EmojiSuggestions,
EmojiSelect,
};
}, []);
<div className="flex w-100 items-center">
<div
className={`${editorStyles.editor} flex w-100 pa2`}
onClick={() => {
ref.current!.focus();
}}
>
<Editor
editorKey={'editor'}
editorState={editorState}
handleKeyCommand={(cmd) => handleKeyCommand(cmd)}
keyBindingFn={myKeyBindingFn}
onChange={onChange}
plugins={plugins}
ref={ref}
placeholder="Type your message and hit ENTER to send"
/>
<MentionSuggestions
open={open}
onOpenChange={onOpenChange}
suggestions={suggestions}
onSearchChange={onSearchChange}
onAddMention={() => {
// get the mention object selected
}}
/>
<EmojiSuggestions />
</div>
<div style={{ margin: '0 .5rem' }}>
<EmojiSelect closeOnEmojiSelect />
</div>
<SendIcon
className="pointer"
style={{ color: '#3F61C5', margin: '0 .5rem' }}
onClick={handleSend}
/>
</div>
I also inspected the elements, the css I wrote in
positionSuggestions are not there. Any way I can place this popover on top?
I had the same issue and I have resolved it using the CSS option.
Create a copy of this file in your project: https://github.com/draft-js-plugins/draft-js-plugins/blob/master/packages/emoji/src/theme.ts
Find emojiSelectPopover class and replace it with below:
emojiSelectPopover: css`
padding: 0 0.3em;
position: absolute;
z-index: 1000;
box-sizing: content-box;
background: #fff;
border: 1px solid #e0e0e0;
box-shadow: 0 4px 30px 0 gainsboro;
transform: scale(1) translateY(-100%);
transform-origin: 1em 0% 0px;
top: 65%;
#media (min-width: 320px) and (max-width: 480px) {
right: 0;
top: 50%;
}
Use your custom theme file(which you have created in #1) in the configuration:
const emojiPlugin = createEmojiPlugin({
selectButtonContent: '😊',
theme: defaultTheme // This should be imported from your created theme file
});
For your scenario, you need to move right: 0; outside of the media query as your emoji button is close to the right side of the browser.

ReactDOM.createPortal modal is mounted on DOM but nothing is displayed on the screen

this is a typescript-next.js project. I have this Modal component:
interface ModalProps {
onCancelModal: () => void;
onAcceptModal: () => void;
acceptEnabled: boolean;
isLoading?: boolean;
title: string;
}
const Modal: React.FC<ModalProps> = (props) => {
let containerRef = useRef<HTMLDivElement | null>(null);
console.log("container", containerRef);
useEffect(() => {
const rootContainer = document.createElement("div");
const parentElem = document.querySelector("#__next");
parentElem?.insertAdjacentElement("afterend", rootContainer);
if (!containerRef.current) {
containerRef.current = rootContainer;
}
return () => rootContainer.remove();
}, []);
return containerRef.current
? ReactDOM.createPortal(
<div className="modal">
<header className="modal__header">
<h1>{props.title}</h1>
</header>
<div className="modal__content">{props.children}</div>
<div className="modal__actions">
<Button design="danger" mode="flat" onClick={props.onCancelModal}>
Cancel
</Button>
<Button
mode="raised"
onClick={props.onAcceptModal}
disabled={!props.acceptEnabled}
loading={props.isLoading}
>
Accept
</Button>
</div>
</div>,
containerRef.current
)
: null;
};
export default Modal;
I pass a custom error to ErrorHandler component:
const ErrorHandler: React.FC<ErrorHandlerProps> = (props) => (
<Fragment>
{props.error && <Backdrop onClick={props.onHandle} />}
{props.error && (
<Modal
title="An Error Occurred"
onCancelModal={props.onHandle}
onAcceptModal={props.onHandle}
acceptEnabled
>
<p>{props.error}</p>
</Modal>
)}
</Fragment>
);
However, Modal component is successfully mounted on the DOM but nothing displays on the screen.
EDIT
I have backdrop and modal components.
// css for backdrop
.backdrop {
width: 100%;
height: 100vh;
background: rgba(0, 0, 0, 0.75);
z-index: 100;
position: fixed;
left: 0;
top: 0;
transition: opacity 0.3s ease-out;
opacity: 1;
}
// css for Modal
.modal {
position: fixed;
width: 90%;
left: 5%;
top: 20vh;
background: white;
border-radius: 5px;
z-index: 200;// I changed this to 999999 but didnot solve the issue
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
}
.modal__header {
border-bottom: 2px solid #3b0062;
}
.modal__header h1 {
font-size: 1.5rem;
color: #3b0062;
margin: 1rem;
}
.modal__content {
padding: 1rem;
}
.modal__actions {
padding: 1rem;
text-align: right;
}
.modal__actions button {
margin: 0 0.5rem;
}
#media (min-width: 768px) {
.modal {
width: 40rem;
left: calc((100% - 40rem) / 2);
}
}
I found the answer after i refresh my memory. I realized that there is another .modal className on elements-styles tab. It points me to the /node_modules/bootstrap/scss/_modal.scss file which also has modal className and it was overriding my custom className.
.modal {
position: fixed;
top: 0;
left: 0;
z-index: $zindex-modal;
display: none;
width: 100%;
height: 100%;
overflow: hidden;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// We deliberately don't use `-webkit-overflow-scrolling: touch;` due to a
// gnarly iOS Safari bug: https://bugs.webkit.org/show_bug.cgi?id=158342
// See also https://github.com/twbs/bootstrap/issues/17695
}

Lazy images load perfectly but have to reload abruptly when they reappear in the viewport

I have managed to lazy load a long list a images in React, with a loader. It works well. However, when scrolling back up, the images have disappeared and have to reload abruptly - without the smooth transition - to go back on screen. Long story short, the user experience is crap. Normally, already loaded images should remain displayed.
How to fix such a performance issue? Is it because of loading="lazy" native img attribute?
Here is a sandbox that reproduce the issue:
https://codesandbox.io/s/trusting-tdd-qlf9h?file=/src/App.tsx
Here is the code:
import React, { useEffect, useRef, useState } from "react";
import styled, { keyframes } from "styled-components";
interface ImageProps {
src: string;
lazy?: boolean;
width?: number;
height?: number;
alt?: string;
}
const Root = styled.div<{ width: number; height: number }>`
border-radius: 8px;
width: ${({ width }) => (width ? `${width}px` : "100%")};
height: ${({ height }) => (height ? `${height}px` : "100%")};
position: relative;
`;
const loading = keyframes`
0% {background-position: -468px 0;}
100% {background-position: 900px 0;}
`;
const Placeholder = styled.div`
position: absolute;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: #f6f7f9;
animation-duration: 1s;
animation-fill-mode: forwards;
animation-iteration-count: infinite;
animation-name: ${loading};
animation-timing-function: linear;
background-image: linear-gradient(
to right,
#f6f7f9 0%,
rgba(175, 164, 164, 0.11) 20%,
#f6f7f9 40%,
#f6f7f8 80%,
#f6f7f9 100%
);
background-repeat: no-repeat;
background-size: 800px 400px;
height: 100%;
`;
const Picture = styled.img<{ loaded: boolean }>`
position: absolute;
width: 100%;
height: 100%;
top: 0;
bottom: 0;
left: 0;
right: 0;
object-fit: cover;
object-position: center;
opacity: ${({ loaded }) => (loaded ? 1 : 0)};
transition: opacity 1s;
`;
export default function Image({
src,
width,
height,
lazy = false,
alt
}: ImageProps) {
const [loaded, setLoaded] = useState(false);
const ref = useRef(null);
useEffect(() => {
if (ref.current && ref.current.complete) {
setLoaded(true);
}
}, []);
return (
<Root width={width} height={height}>
<Placeholder aria-hidden="true" />
<Picture
loading={lazy ? "lazy" : null}
src={src}
alt={alt}
width={width}
height={height}
ref={ref}
onLoad={() => setLoaded(true)}
loaded={loaded}
/>
</Root>
);
}
Bonus question: how to apply a padding to all images directly from the Wrapper container (the one that has display: flex + flex-wrap:wrap in App.tsx?

Resources