Add fade out animation on dropped item with React Beautiful DND - css

I am using the React Beautiful DND library to drag items(square divs) between columns or reordered in the same column. I followed their Egghead video tutorial to change the background color of the div as it's being dragged. When it gets dropped, it switches back to the default color of all other items. I want it to slowly fade(like 1 second maybe) to the default color after it is dropped.
Here is my current code styling for the div as it's being dragged and dropped. I added the transition line, that but is not doing anything.
const MyOrder = styled.div`
background-color: ${(props) =>
props.isDragging ? '#4FB740' : '#193DF4'};
transition: background-color 1s ease;
`;
I have tried adding this code to the onDragEnd event:
setDroppedOrderID(theOrder.orderNumber);
setTimeout(() => {
setDroppedOrderID('');
}, 2000);
And I made the order div that gets dragged look like this:
<MyOrder
id={orderNumber}
className={`order size-${size} ${
droppedOrderID === orderNumber ? ' dropped' : ''
} ${palletLoc === 'OUTSIDE' ? 'outside' : ''}`}
But it is buggy if someone tries to drag the same item in less than the 2 second time interval.

You can actually style the drop and do animation
See working demo & full code here in the codesandbox
You need to use isDropAnimating property from the snapshot to check if animation is being done so that you can conditionally return the original style.
code snippet
const OpportunityContainer = styled.div`
border: 1px solid #ddd;
border-radius: 0.3rem;
background: #fff;
padding: 1rem;
margin-bottom: 0.8rem;
transition: background-color 5s ease;
background-color: ${props => (props.isDragging ? "#4FB740" : "#193DF4")};
`;
function getStyle(style, snapshot) {
if (!snapshot.isDropAnimating) {
return style;
}
// patching the existing style
return {
...style,
transition: `all 3s ease`,
backgroundColor: "blue"
};
}
const Opportunity = React.memo(({ index, id, title }) => {
return (
<Draggable draggableId={id} index={index}>
{(provided, snapshot) => (
<OpportunityContainer
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
isDragging={snapshot.isDragging && !snapshot.isDropAnimating}
style={getStyle(provided.draggableProps.style, snapshot)}
>
{title}
</OpportunityContainer>
)}
</Draggable>
);
});
export default Opportunity;
Note - Make sure to read this note in the library documentation. isDragging will be true until animation/fade-out is completed. Therefore try to provide less duration for your animation (eg: 1 second or less than 1 second)

Related

REACT Cant convert my styling to inline styles

I need to take some button styling and apply it to some inline styling.
I thought this would be a simple copy and paste but when I try that I have a whole host of errors.
I've tried to convert the styles to inline format but I keep chasing errors I don't know how to resolve.
Thanks for the help.
The following is the button I am trying to apply styling to.
<BtnWrap>
{navToPage && navToPage.startsWith("/") ? (
//if it is linked to another page use router link
<LinkR to={navToPage} style={{}}>
{navToPage}
</LinkR>
) : (
//else use the smart link component
<Button
to={navToPage}
smooth={true}
duration={500}
spy={true}
exact="true"
offset={-80}
primary={primary ? 1 : 0}
dark={dark ? 1 : 0}
dark2={dark2 ? 1 : 0}
>
{buttonLabel}
</Button>
)}
</BtnWrap>
This is the button styling I am trying to convert to inline styling.
export const Button = styled(Link)`
border-radius: 50px;
background: ${({ primary }) => (primary ? "#ca1f27" : "#010606")};
white-space: nowrap;
padding: ${({ big }) => (big ? "14px 48px" : "12px 30px")};
color: ${({ dark }) => (dark ? "#010606" : "#fff")};
font-size: ${({ fontBig }) => (fontBig ? "20px" : "16px")};
outline: none;
border: none;
cursor: pointer;
display: flex;
justify-content: center;
align-items: center;
transition: all 0.2s ease-in-out;
&:hover {
transition: all 0.2s ease-in-out;
background: ${({ primary }) => (primary ? "#fff" : "#ca1f27")};
}
`;
Styles in React.js are a kind of object that is passed inside the style property like this
style={{ marginTop: 10, backgroundColor: 'red' }}
I leave some references that can help you:
https://www.w3schools.com/react/react_css.asp
https://www.pluralsight.com/guides/inline-styling-with-react

CSS Transition not working with react and styled components

I have a problem with css transition, i use styled component and the element add its className based on the changing of react useState which is triggered by onClick back and forth,
here is the part of the code that dont work as expected:
export const SearchProduct = ({ product }) => {
const [descStatus, setdescStatus] = useState(false);
const handleDesc = () => {
setdescStatus(!descStatus);
};
return (
<li>
<Item>
<Photo>
<img src={`${product.productImg}`} alt={product.productTitle}></img>
</Photo>
<Information>
<h3> {product.productTitle} </h3>
<Desclook>
<div className={descStatus ? 'active' : null} onClick={handleDesc}>
{descStatus ? 'Close' : 'See Desc ...'}
</div>
</Desclook>
{descStatus && (
<Description --> this is part that dont work
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Here is the styled components part :
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
&.content {
height: 0;
overflow: hidden;
}
&.showContent {
height: 70px;
overflow-y: scroll;
}
`;
Does anybody have any idea what happened with my code here cause i'm kinda new to react and styled component
Remove the check for descStatus and always render <Description> instead.
So instead of this:
{descStatus && (
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
)}
Do this:
<Description
className={descStatus ? 'showContent content' : 'content'}
>
{product.productDesc}
</Description>
The reason behind this is a CSS transition needs to transition from a different value than the current value. In your code when you check if descStatus is true before rendering, your Description component will never have the className="content" and will always be rendered initially with a height of 70px, so no transition will occur.
Hey you can solve it easily if you send the state as a prop instead of setting className
And you should update the state based on previous state and as useState setter sets the state asynchronously you might need asynchronous version of setState this is irrelevant for this problem but can cause problems in some cases
const handleDesc = () => {
setdescStatus(p => !p);
};
For the styled component part
<Description --> this is part that dont work
show={descStatus}
>
{product.productDesc}
</Description>
and inside the styled component you can handle it like
import styled,{css} from 'styled-components';
const Description = styled.p`
margin: 10px;
padding: 0;
transition: all 0.3s ease-in-out;
//content class styles applied by default
height: 0;
overflow: hidden;
//these styles will be applied only if show is true (css you can import from
//styled component as a named import)
${({show}) => show && css`
height: 70px;
overflow-y: scroll;
`}
`;

move Animation between button on reactjs

I am working on a project and I am new with animation and my objective is from a group of buttons when the user click on the button become active and a line will move from button to button.
I managed to make the button active and change color and etc, the only thing i still can`t make is that line to move from button to button.
This is my Reactjs code
class AnimateButtonArea extends Component {
constructor(props){
super(props)
this.state = { active: 0 };
}
setActive = (key) => {
this.setState({ active: key });
}
render() {
return (
<div className = "AnimateButtonArea">
{data.AnimateButton.map((animateButton, key) => {
return(
<div key = {key} className = { this.state.active === key ? "AnimateButton active" : "AnimateButton"} >
<Button type = {this.state.active === key ? "SofiaProBlackSmallActive" : "SofiaProBlackSmall"} onClick = {() => { this.setActive(key) }} text = {animateButton.button} />
<div className = "animatedLine" />
</div>
)
})}
</div>
)
}
}
export default AnimateButtonArea;
And this is my Sass code
.AnimateButtonArea
display: inline-block
.AnimateButton
display: inline-block
margin-right: 147px
.AnimateButton.active .animatedLine
position: relative
height: 8px
left: 24px
background: var(--yellow)
border-radius: 40px
text-align: center
animation: grow 0.4s ease-out
#keyframes grow
0%
width: 0%
100%
width: 100%
UPDATE
Now I can move the line but now i want to increase the line and then decrease and move it
Here's the updated Reactjs part
setActive = (key) => {
this.setState({ active: key })
var difference = key - this.state.active
leftActive += 300 * difference
style = { width: `calc(${leftActive}px + 120px)`}
setTimeout(()=> this.beActive(leftActive), 1000)
}
beActive = (leftActive) => {
style = { marginLeft: `calc(${leftActive}px)`, with: `120px`}
console.log(style);
}
To move between positions, the line should be an unique object outside of the mapped array. Then you can manipulate the positioning (with margin, transform or left) based on the key/(array.length-1) with inline styles.
Set css transition: all 1s ease; to animate.
I would pass the width to state (this.state.activeWidth) and include it in the line style object. {width:this.state.activeWidth}

React JS: How to animate conditionally rendered components?

Example is a functional component in which I am rendering a div conditionally. I want this div to fade-in when rendered conditionally and fade-out vice versa.
For that, I have maintained two local state variables: render and fadeIn which are computed based on show prop passed down to the Example component.
What I've done is:
When show prop it true, I set render as true, so the div renders conditionally and after a timeout of 10ms I set fadeIn as true which will set CSS classname for my div as show.
When show prop it false, I set fadeIn as false, which will set CSS classname for my div as hide and after a timeout of 200ms (transition time in CSS) I set render as false so the div is hidden conditionally.
Code:
interface Props {
show: boolean;
}
const Example: React.FC<Props> = ({ show, }) => {
const [render, setRender] = useState(false);
const [fadeIn, setFadeIn] = useState(false);
useEffect(() => {
if (show) {
// render component conditionally
setRender(show);
// change state to for conditional CSS classname which will
// animate opacity, I had to give a timeout of 10ms else the
// component shows up abruptly
setTimeout(() => {
setFadeIn(show);
}, 10);
} else {
// change state to change component classname for opacity animation
setFadeIn(false);
// hide component conditionally after 200 ms
// because that's the transition time in CSS
setTimeout(() => {
setRender(false);
}, 200);
}
}, [
show,
]);
return (
<div>
{render && (
<div className={`container ${fadeIn ? 'show' : 'hide'}`} />
)}
</div>
);
};
Stylesheet:
.container {
width: 100px;
height: 100px;
background-color: black;
transition: opacity 0.2s ease;
}
.show {
opacity: 1;
}
.hide {
opacity: 0;
}
I believe this is not a good coding practice to achieve the functionality and should maintain only one local state in my component. I need your suggestions on how I can solve this in a better way without using any 3rd Party Library.
Thanks :)
const [render, setRender] = useState(false);
useEffect(() => {
if(show) {
setTimeout(() => {
setRender(true);
}, 2000);
} else {
setRender(false);
}
}, [show]);
<div className={cs(s.render, render ? 'show' : undefined)}>
<p>{content}</p>
</div>
Css:
.render {
...,
visibility: hidden;
opacity: 0;
transition: all 0.6s ease;
}
.show {
visibility: visible;
opacity: 1;
}
Hope be helpful.

React css transition does not work correctly

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

Resources