First of all, sorry for the ambiguous title.
I'm trying to make a spinner that has a label.
And my spinner component is like below.
const rotate360 = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const Circle = styled.div`
border: 5px solid ${({ theme }) => theme.color.grey};
border-radius: 50%;
border-top-color: #fff;
width: ${({ size }) => size}px;
height: ${({ size }) => size}px;
animation: ${rotate360} 1s ease-in-out infinite;
`
const Label = styled.p`
`
const Wrapper = styled.div`
// I'm not sure what to add here...
`
const Spinner = ({
size,
text
}) => {
return (
<Wrapper>
<Label>{text}</Label>
<Circle size={size} />
</Wrapper>
)
}
My question is how can I make my Wrapper components to know the width of child elements without making any Refs which is inconvenient and makes component larger.
My point is to calculate each of parent's children's width and then compare them and finally let the longest one be the parent's width.
It's like if one parent div has two children. One has a length of 100px and another has a length of 200px. Then parent's length would be 200px.
Think of my question, I realized that I don't have to calculate the width of child elements.
Instead, I just made them center aligned by the code below.
const Wrapper = styled.div`
display: block;
padding: 1em;
text-align: center;
& > ${Circle} {
margin: auto;
margin-bottom: 20px;
}
`
Lovely result, right ?
Related
I have a css question. when a number gets to double digits, the gap between itself and left right button increased. I tried to use boxsize property, but it does not work.
import { useSelector, useDispatch } from "react-redux";
import { decrement, increment } from "./counterSlice";
import "./Counter.css";
const Counter = () => {
const count = useSelector((state) => state.counter.value);
const clicks = useSelector((state) => state.counter.clicks);
const dispatch = useDispatch();
return (
<div className="counter">
<div>
<button
aria-label="increment value"
onClick={() => dispatch(increment())}
>
Increment
</button>
<span className="text">{count}</span>
<button
aria-label="decrement value"
onClick={() => dispatch(decrement())}
>
decrement
</button>
<p>{clicks}</p>
</div>
</div>
);
};
export default Counter;
.counter {
margin: 60px auto;
width: 20rem;
background: aqua;
padding: 1.25rem;
border-radius: 15px;
text-align: center;
box-sizing: border-box;
}
.text {
display: inline-block;
margin: 0 15px;
}
I tried boxsize property and increased the gap between them. Those dont work.
I want to maintain the gap size when a number gets to double digits
[enter image description herenter image description heree](https://i.stack.imgur.com/AtX9L.png)
If you want to fix the size of the button you would need either to turn it into a div element with fixed width or add display: inline-block with width: 50px to .text as well as text-align: center to keep text in the middle.
If you're using margins and paddings without width that is designed to contain children - that would always end up being content-dependant. Either remove them or keep them minimal.
try setting the display of the .counter to display : flex;
then set the propertyjustify-content : center;
and set a max-width:20rem;
the pictures below are to explain flex properties
I am implementing drop-down list using styled-component in react. In the process, I have two questions.
First, when dropDownVisible changes from true to false, why doesn't the animation effect apply and it disappears immediately? How can I improve the animation effect? Like when this list goes down, I want to make it gradually when it goes up.
Second, when StyledDropdown is dropped down, I want it to drop down behind the StyledHead, so I set the z-index property like that. I want the StyledHead to be always on top, so I'm curious why the StyledHead is hidden as the StyledDropdown drops down, even though I gave the z-index property bigger.
The source code is roughly structured like this:
// AApage.jsx
import { useEffect, useState, useRef } from 'react';
import { MdArrowDropDown, MdArrowDropUp } from 'react-icons/md';
import styled, { keyframes } from 'styled-components';
const dropAnimation = keyframes`
0% {
transform : translateY(-300px);
display : none;
}
100% {
transform : translateY(0);
}
`;
const StyledHead = styled.div`
width: 100px;
height: 100px;
background-color: red;
z-index: 11;
`;
const StyledDropdown = styled.div`
width: 100px;
height: 300px;
background-color: #d9d9d9;
border-radius: 0px 0px 10px 10px;
z-index: 3;
animation: ${dropAnimation} 1s alternate;
`;
const AApage = () => {
const [dropDownVisible, setDropDownVisible] = useState<boolean>(false);
const toggleDropDownVisible = () => {
setDropDownVisible((prev) => !prev);
};
return (
<>
<StyledHead>
<div>Dropdown</div>
<span>{`${dropDownVisible}`}</span>
{dropDownVisible ? (
<MdArrowDropUp
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropUp>
) : (
<MdArrowDropDown
onClick={() => {
toggleDropDownVisible();
}}
></MdArrowDropDown>
)}
</StyledHead>
{dropDownVisible ? (
<StyledDropdown>
<div>temp data</div>
<div>temp data</div>
<div>temp data</div>
</StyledDropdown>
) : (
<></>
)}
</>
);
};
export default AApage;
I have spent countless hours trying to figure out why my dropdown that is opened/closed by a burger menu icon click is sitting in front of the navbar even though I have specified z-indexes, overflows and positions. This issue is only happening on the MobileNav component below. MobileNav consists of a burger icon and the actual dropdown. Once the burger icon is clicked, the dropdown will either close or open. Currently It is displaying above the nav component and I am having a very hard time figuring out why. Any help will be much appreciated.
Vid to see the dropdown's behavior: https://www.youtube.com/watch?v=zOBnb6r_RN4&ab_channel=TylerOreskey
The dropdown is supposed to come out from the bottom of the navbar and close up into the bottom of the navbar.
Navbar Component: Renders MobileNav component
const Navbar = (props) => {
const [showDropdown, setShowDropdown] = useState(false);
const dropdownToggleHandler = () => setShowDropdown(!showDropdown);
const dropdownClosedHandler = () => setShowDropdown(false);
return (
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative",
}}
>
<nav className={classes.MobileNav}>
<MobileNav
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
dropdownToggleHandler={dropdownToggleHandler}
/>
</nav>
</header>
);
};
export default memo(Navbar);
CSS file for Navbar component: z-index is not working in here.
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
z-index: 500;
}
#media (max-width: 500px) {
.DesktopNav {
display: none;
}
}
#media (min-width: 500px) {
.MobileNav {
display: none;
}
}
MobileNav component
const MobileNav = (props) => {
return (
<div className={classes.MobileNav}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
<Dropdown open={props.open} allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default MobileNav;
CSS file for MobileNav component
.MobileNav {
overflow: hidden;
}
Dropdown component: (This is displayed above the Navbar component and I cannot get it to be behind the navbar component).
const Dropdown = (props) => {
let attachedClasses = [classes.Dropdown, classes.Close];
if (props.open) {
attachedClasses = [classes.Dropdown, classes.Open];
}
return (
<div className={attachedClasses.join(" ")}>
<NavigationItems allNavigationRefs={props.allNavigationRefs} />
</div>
);
};
export default Dropdown;
CSS file for Dropdown component: z-index is not working in here.
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
z-index: 400;
display: block;
}
.Open {
transform: translate(0, 25%);
}
.Close {
transform: translate(0, -75%);
}
You are confused on how z-index works.
Consider each level in your tree as a layer.
lets say that Navbar is layer 0, MobileNav is then layer 1, and its children are on layer 2.
By default z-index is calculated among children of the same layer. This is true when the position attribute is on default static. When you alter this to relative you can instruct which layers are going to interuct with each other in a more immediate way.
Having 500 z-index on Navbar will make no sense to MobileNav. It is not his sibling, it's his child.
Here is a possible solution if you can alter the DOM tree
<header
className={classes.Navbar}
style={{
position: props.passedNavbar ? "fixed" : "relative"
}}
>
<nav className={classes.Navbar}>
<DropdownToggle clicked={props.dropdownToggleHandler} />
</nav>
<MobileNav
className={classes.MobileNav}
allNavigationRefs={props.allNavigationRefs}
scrollToDiv={props.scrollToDiv}
open={showDropdown}
closed={dropdownClosedHandler}
/>
</header>
and here is an answer if you can alter the CSS
.Navbar {
top: 0;
height: 50px;
background-color: hsl(213, 27%, 15%);
border-bottom: #00bfff 3px solid;
width: 100%;
position: relative;
}
...
.Dropdown {
background: hsl(212, 87%, 3%);
height: 200px;
transition: transform 0.3s ease-out;
display: block;
position: relative;
z-index: -1;
}
I have a component where when o clicking the button, the div with more info will slideUp and slideDown.
Below the code and css style
import { CSSTransition } from "react-transition-group";
const Card = () => {
const [showMoreInfo, setShowMoreInfo] = useState(false);
return (
<div className="Card">
<ButtonShowMore isOpen={showMoreInfo} click={() => setShowMoreInfo(!showMoreInfo)} />
<CSSTransition in={showMoreInfo} classNames="Card-Details" timeout={1000}>
<div>
{showMoreInfo && (
<>
<p>details</p>
<p>details</p>
</>
)}
</div>
</CSSTransition>
</div>
);
};
.Card-Details-enter {
height: 0px;
}
.Card-Details-enter-active {
height: 100%;
-webkit-transition: height 1s ease;
-moz-transition: height 1s ease;
-o-transition: height 1s ease;
transition: height 1s ease;
}
.Card-Details-enter-done {
height: 100%;
}
.Card-Details-exit {
height: 100%;
}
.Card-Details-exit-active {
height: 0px;
-webkit-transition: height 1s ease;
-moz-transition: height 1s ease;
-o-transition: height 1s ease;
transition: height 1s ease;
}
.Card-Details-exit-done {
height: 0px;
}
But it doesnt work, I don't know why. I tred to put the transition to the parent element like here
and add transition to the class *-exit-done like here, and nothing helped.
The reason why it doesn't work is because transitioning on percentage height isn't quite what you would expect.
Percentage height and width in CSS refer to their parent's height and width rather than their own height.
MDN Percentage
The CSS data type represents a percentage value. It is
often used to define a size as relative to an element's parent object.
Numerous properties can use percentages, such as width, height,
margin, padding, and font-size.
Example with the same element transitioning using a percentage height/width. One has a container element with a height/width of 50px, and the other doesn't.
div.container {
width: 50px;
height: 50px;
}
div.transition {
width: 100%;
height: 100%;
background: red;
transition: width 2s, height 4s;
}
div.transition:hover {
width: 300%;
height: 500%;
}
Transition div with a 50px container
<div class="container">
<div class="transition">
<p>test</p>
</div>
</div>
Transition div without a container:
<div class="transition">
<p>test</p>
</div>
What we'd actually want is to transition from 0px to auto height. Unfortunately browsers don't support transitioning on auto height.
A good write up of this is in Using CSS Transitions Auto Dimensions include some approaches to get what you want and their downsides.
Why hasn’t this problem been fixed at the browser level?
According to the Mozilla Developer Network docs, auto values have been
intentionally excluded from the CSS transitions spec. It looks like
it’s been requested by a few people, but when you think about it, it
makes at least a little sense that it hasn’t been included. The
browser process that re-calculates the sizes and positions of all
elements based on their content and the way they interact with each
other (known as “reflow”) is expensive. If you were to transition an
element into a height of auto, the browser would have to perform a
reflow for every stage of that animation, to determine how all the
other elements should move. This couldn’t be cached or calculated in a
simple way, since it doesn’t know the starting and/or ending values
until the moment the transition happens. This would significantly
complicate the math that has to be done under the hood and probably
degrade performance in a way that might not be obvious to the
developer.
How can I transition height: 0; to height: auto; using CSS? also has some good workarounds, though there really is no magic bullet for this.
It is definitely a very well known issue, and there's a request for the spec to change to allow transitions on auto, though I don't think it's gone anywhere yet.
As for support for the type of transition you are working on in React Transition Group:
Slide Down Animation and Trying to fade out element then slide up both have the same answer overall: pointing at how React Bootstrap's Collapse component does it.
You need to rely on finding the dom node's actual height and using that as part of the transition:
getDimension() {
return typeof this.props.dimension === 'function'
? this.props.dimension()
: this.props.dimension;
}
// for testing
_getScrollDimensionValue(elem, dimension) {
return `${elem[`scroll${capitalize(dimension)}`]}px`;
}
/* -- Expanding -- */
handleEnter = (elem) => {
elem.style[this.getDimension()] = '0';
}
handleEntering = (elem) => {
const dimension = this.getDimension();
elem.style[dimension] = this._getScrollDimensionValue(elem, dimension);
}
handleEntered = (elem) => {
elem.style[this.getDimension()] = null;
}
/* -- Collapsing -- */
handleExit = (elem) => {
const dimension = this.getDimension();
elem.style[dimension] = `${this.props.getDimensionValue(dimension, elem)}px`;
triggerBrowserReflow(elem);
}
handleExiting = (elem) => {
elem.style[this.getDimension()] = '0';
}
A quick and dirty example of using the functionality from the Collapse class for a working example of the code using a less fully featured solution (note, based heavily on the Collapse.js code linked above):
const { Transition } = ReactTransitionGroup;
const { EXITED, ENTERED, ENTERING, EXITING } = Transition;
const { useState } = React;
// Quick and dirty classNames functionality
const classNames = (...names) => names.filter((name) => name).join(' ');
const ButtonShowMore = ({ isOpen, click }) => {
return <button onClick={click}>{isOpen ? 'Close' : 'Open'}</button>;
};
// Heavily based on https://github.com/react-bootstrap/react-bootstrap/blob/next/src/Collapse.js#L150
// for the purpose of demonstration without just pulling in the module:
function triggerBrowserReflow(node) {
node.offsetHeight; // eslint-disable-line no-unused-expressions
}
const collapseStyles = {
[EXITED]: 'collapse',
[EXITING]: 'collapsing',
[ENTERING]: 'collapsing',
[ENTERED]: 'collapse in',
};
const Collapse = ({ children, ...props }) => {
const handleEnter = (elem) => (elem.style.height = '0');
const handleEntering = (elem) =>
(elem.style.height = `${elem.scrollHeight}px`);
const handleEntered = (elem) => (elem.style.height = null);
const handleExit = (elem) => {
elem.style.height = `${elem.scrollHeight}px`;
triggerBrowserReflow(elem);
};
const handleExiting = (elem) => (elem.style.height = '0');
return (
<Transition
{...props}
onEnter={handleEnter}
onEntering={handleEntering}
onEntered={handleEntered}
onExit={handleExit}
onExiting={handleExiting}
>
{(state, innerProps) =>
React.cloneElement(children, {
...innerProps,
className: classNames(
props.className,
children.props.className,
collapseStyles[state]
),
})
}
</Transition>
);
};
const Card = () => {
const [showMoreInfo, setShowMoreInfo] = useState(false);
return (
<div className="Card">
<ButtonShowMore
isOpen={showMoreInfo}
click={() => setShowMoreInfo(!showMoreInfo)}
/>
<Collapse in={showMoreInfo} className="Card-Details" timeout={1000}>
<div style={{ height: 0 }}>
<p>details</p>
<p>details</p>
</div>
</Collapse>
</div>
);
};
ReactDOM.render(<Card />, document.querySelector('#root'));
.collapsing {
-webkit-transition: height 1s ease;
-moz-transition: height 1s ease;
-o-transition: height 1s ease;
transition: height 1s ease;
overflow: hidden;
}
.collapse {
overflow: hidden;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-transition-group/4.4.1/react-transition-group.min.js"></script>
<div id="root" />
I've written a React app, using CSS transitions. But those transitions does not work correctly in some of the components. In my app, only the components who are moving upwards works well, those who are moving downwards moves instantly without animation. (I want them both moves with animation.)
Here is the CSS I used there:
div.canvas {
position: absolute;
top: 90px;
left: 60px;
width: 640px;
height: 480px;
border: 1px solid #999;
background: white;
}
div.canvas-rect {
position: relative;
margin-left: 20px;
margin-top: 10px;
height: 20px;
background: green;
transition: all 1s linear;
-moz-transition: all 1s linear; /* Firefox 4 */
-webkit-transition: all 1s linear; /* Safari 和 Chrome */
-o-transition: all 1s linear; /* Opera */
}
UPDATED:
I also built a codepen.io project to show the problem. It has the complete code of this demo project.
I've tried to add a log entry to componentDidUpdate, componentDidMount and componentWillUnmount methods to show whether these component are re-created or updated, it shows that they are all updated (not re-created, or removed) every second.
Well, after I started a bounty because I also have this problem I finally found what seems to be the problem.
When you are using absolute position (or relative, as in your case), if you re-render the whole list every time, React will re-order the elements in the DOM (as you said, the elements are not being recreated, just updated). But this creates the problem with the transitions... apparently, if you move an element while the transition is running then you end up cutting the animation.
So, for cases in which you want to use position absolute, the key concept is to render the containers of your elements once (in this case, just divs) and only change the inner contents based on the new order. If you need to add more elements, just add them at the end.
I modified your codepen so that it reflects what I am saying. My example is very dumb because I just created 4 ad-hoc divs, but it illustrates the idea: create as many containers as you need, but DO NOT use a map that recreates them every time, or your transitions will be cut.
https://codepen.io/damianmr/pen/boEmmy?editors=0110
const ArrList = ({
arr
}) => {
return (
<div style={{position: 'relative'}}>
<div className={`element element-${arr[0]} index-${arr[0]}`}>{arr[0]}</div>
<div className={`element element-${arr[1]} index-${arr[1]}`}>{arr[1]}</div>
<div className={`element element-${arr[2]} index-${arr[2]}`}>{arr[2]}</div>
<div className={`element element-${arr[3]} index-${arr[3]}`}>{arr[3]}</div>
</div>
);
}
So, the problem is basically how you create a static list of containers and how you iterate through that list so that the first container renders the first element of your data, the second container the second element, etc.
Hope that it helps, this problem was driving me crazy too! :)
I know this wasn't the case, but since I got here also looking for React css transition does not work correctly, I just wanted to share:
If you create an element using arrow functions inside render, it won't get properly animated, since a new componente is always being created.
You should create a function outside and invoke it in 'render'.
You can trick React by using index as key. If you think about el, and index as starting position (index) and end position (el), the element has moved to the old end position by the end of the transition, and by when it's there, it's taken over by the new start position and (index) is switched to match the new setup. This is because when you set key in an element in react, the virtual DOM will always interpret it as it is the same element. And for the sake of it, you're right in setting index as the "id" in general.
I made a working example only by switching index/el (and setting element position to absolute).
const {combineReducers, createStore} = Redux;
const { Provider, connect } = ReactRedux;
const ArrList = ({
arr
}) => (
<div>{
arr.map((el, index)=>
<div
key={""+index}
className={`element element-${el}` + ` index-${el}`}
>
{el}
</div>) }
</div>
)
const mapStateToArrList = (state) => {
return {
arr: state.appReducer.arr
}
};
const App = connect(mapStateToArrList, null)(ArrList);
const initialState = {
arr: [1, 2, 3, 4]
}
const appReducer = (state = initialState, action) => {
switch(action.type) {
case "tick":
return {
...state,
arr: _.shuffle(state.arr)
}
default:
return state
}
}
const reducer = combineReducers({
appReducer
})
const store = createStore(reducer)
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
)
const dispatcher = () => {
store.dispatch({
type: "tick"
})
setTimeout(dispatcher, 1000)
}
dispatcher()
.element {
position: absolute;
height: 20px;
background: green;
margin-left: 20px;
margin-top: 20px;
text-align: right;
color: white;
line-height: 20px;
transition: all 1s ease-in;
-moz-transition: all 1s ease-in; /* Firefox 4 */
-webkit-transition: all 1s ease-in; /* Safari 和 Chrome */
-o-transition: all 1s ease-in; /* Opera */
}
.element-1 {
width: 20px;
}
.element-2 {
width: 40px;
}
.element-3 {
width: 60px;
}
.element-4 {
width: 80px;
}
.index-1 {
top: 20px;
}
.index-2 {
top: 40px;
}
.index-3 {
top: 60px;
}
.index-4 {
top: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/redux/3.7.2/redux.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-redux/5.0.6/react-redux.js"></script>
<div id="root"></div>
If you are removing the element from the virtual DOM, then the react will update its contents, so you won't see the animations. What you can do is either use react-transition-group OR tell your app to wait x ms before updating the dom once the event is called OR use visibility to toggle between hidden and showing instead of removing it completely from the DOM.
You did recreate DOM elements each time.
You should define collect key value.
I changed your key value '' + el to '' + index.
<div key={'' + index} className={'element element-' + el + ' index-' + index} >
Just change css properties only :)
I''ll just leave that here in case helps somebody but to me it was solved by moving from
export default function Contact(props) {...}
To:
const Contact = (props) => {...}
export default Contact