如何在 ReactJS 中没有任何外部库的情况下进行项目进入和项目离开的转换?
how to make transition for item enter and item leave without any external library in ReactJS?
每当用户单击按钮而不使用任何外部库时,我都想制作吐司动画。每当用户单击按钮时,我都会为 toast enter 设置动画。我试图为吐司设置动画以使其离开,但点击 X 按钮后吐司并未从 UI 中移除。
Toast.js
import React from "react";
import { connect } from "react-redux";
import { deleteToast } from "./redux/action/actionCreators";
import "./toast.scss";
const Toast = (props) => {
const { toasts, deleteToast } = props;
return (
<div className="toast-container">
{toasts.map((toast, index) => {
return (
<div className={`toast bg-${toast.type} toast-enter`} key={index}>
<div className="toast-header">
<p>{toast.title}</p>
<button onClick={() => deleteToast(toast.id)}>X</button>
</div>
{toast.description && (
<div className="toast-description">
<p>{toast.description}</p>
</div>
)}
</div>
);
})}
</div>
);
};
const mapStateToProps = (state) => ({
toasts: state.toastReducer.toasts,
});
const mapDispatchToProps = (dispatch) => ({
deleteToast: (toastId) => dispatch(deleteToast(toastId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Toast);
Toast.scss
$bg-danger: #dc3545;
$bg-warning: #ffc107;
$bg-info: #0d6efd;
$bg-success: #198754;
.toast-container {
position: absolute;
right: 1rem;
bottom: 1rem;
z-index: 999;
.toast {
padding: 0.5rem 1rem;
opacity: 0.9;
color: #fff;
width: 250px;
margin-top: 0.5rem;
&:hover {
opacity: 1;
}
}
.toast-enter {
animation: toast-enter-animation 0.3s linear 1;
}
.toast-leave {
animation: toast-leave-animation 0.3s linear 1;
}
.toast-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.toast-header button {
background-color: inherit;
outline: none;
border: none;
color: inherit;
&:hover {
cursor: pointer;
}
}
.bg-danger {
background-color: $bg-danger;
}
.bg-warning {
background-color: $bg-warning;
}
.bg-info {
background-color: $bg-info;
}
.bg-success {
background-color: $bg-success;
}
}
@mixin keyframes($name) {
@-webkit-keyframes #{$name} {
@content;
}
@-moz-keyframes #{$name} {
@content;
}
@-ms-keyframes #{$name} {
@content;
}
@keyframes #{$name} {
@content;
}
}
@include keyframes(toast-enter-animation) {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
@include keyframes(toast-leave-animation) {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
App.js
import React from "react";
import { connect } from "react-redux";
import { addToast } from "./Commons/Toast/redux/action/actionCreators";
const App = (props) => {
const { addToast } = props;
return (
<button
onClick={() =>
addToast({
title: "Toast title",
type: "success",
description: "Toast description",
})
}
>
Click
</button>
);
};
const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({
addToast: (toast) => dispatch(addToast(toast)),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
我也能够为项目进入和离开进行转换
Toast.js
const Toasts = (props) => {
const { toasts } = props;
return (
<div className="toast-container">
{toasts.map((toast, index) => (
<ToastItem toast={toast} key={index} />
))}
</div>
);
};
Toast.scss
$margin-right: 1rem;
$margin-bottom: 1rem;
.toast-container {
position: absolute;
right: $margin-right;
bottom: $margin-bottom;
z-index: 999;
}
ToastItem.js
const performAsyncTaskAfter = 300;
const ToastItem = (props) => {
const [shouldDelete, setShouldDelete] = useState(true);
const { toast, deleteToast } = props;
const removeToast = () => {
setShouldDelete(true);
setTimeout(() => {
deleteToast(toast.id);
}, performAsyncTaskAfter);
};
useEffect(() => {
setTimeout(() => {
setShouldDelete(false);
}, performAsyncTaskAfter);
}, []);
return (
<div
className={`toast bg-${toast.type} ${!shouldDelete ? `toast-enter` : ""}`}
>
<div className="toast-header">
<p>{toast.title}</p>
<button onClick={removeToast}>X</button>
</div>
{toast.description && (
<div className="toast-description">
<p>{toast.description}</p>
</div>
)}
</div>
);
};
ToastItem.scss
$bg-danger: #dc3545;
$bg-warning: #ffc107;
$bg-info: #0d6efd;
$bg-success: #198754;
.toast {
padding: 0.5rem 1rem;
opacity: 0.9;
color: #fff;
width: 250px;
margin-top: 0.5rem;
transform: translateX(calc(100% + 1rem));
transition: all 0.3s;
&:hover {
opacity: 1;
}
}
.toast-enter {
transform: translateX(0);
}
.toast-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.toast-header button {
background-color: inherit;
outline: none;
border: none;
color: inherit;
&:hover {
cursor: pointer;
}
}
.bg-danger {
background-color: $bg-danger;
}
.bg-warning {
background-color: $bg-warning;
}
.bg-info {
background-color: $bg-info;
}
.bg-success {
background-color: $bg-success;
}
每当用户单击按钮而不使用任何外部库时,我都想制作吐司动画。每当用户单击按钮时,我都会为 toast enter 设置动画。我试图为吐司设置动画以使其离开,但点击 X 按钮后吐司并未从 UI 中移除。
Toast.js
import React from "react";
import { connect } from "react-redux";
import { deleteToast } from "./redux/action/actionCreators";
import "./toast.scss";
const Toast = (props) => {
const { toasts, deleteToast } = props;
return (
<div className="toast-container">
{toasts.map((toast, index) => {
return (
<div className={`toast bg-${toast.type} toast-enter`} key={index}>
<div className="toast-header">
<p>{toast.title}</p>
<button onClick={() => deleteToast(toast.id)}>X</button>
</div>
{toast.description && (
<div className="toast-description">
<p>{toast.description}</p>
</div>
)}
</div>
);
})}
</div>
);
};
const mapStateToProps = (state) => ({
toasts: state.toastReducer.toasts,
});
const mapDispatchToProps = (dispatch) => ({
deleteToast: (toastId) => dispatch(deleteToast(toastId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(Toast);
Toast.scss
$bg-danger: #dc3545;
$bg-warning: #ffc107;
$bg-info: #0d6efd;
$bg-success: #198754;
.toast-container {
position: absolute;
right: 1rem;
bottom: 1rem;
z-index: 999;
.toast {
padding: 0.5rem 1rem;
opacity: 0.9;
color: #fff;
width: 250px;
margin-top: 0.5rem;
&:hover {
opacity: 1;
}
}
.toast-enter {
animation: toast-enter-animation 0.3s linear 1;
}
.toast-leave {
animation: toast-leave-animation 0.3s linear 1;
}
.toast-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.toast-header button {
background-color: inherit;
outline: none;
border: none;
color: inherit;
&:hover {
cursor: pointer;
}
}
.bg-danger {
background-color: $bg-danger;
}
.bg-warning {
background-color: $bg-warning;
}
.bg-info {
background-color: $bg-info;
}
.bg-success {
background-color: $bg-success;
}
}
@mixin keyframes($name) {
@-webkit-keyframes #{$name} {
@content;
}
@-moz-keyframes #{$name} {
@content;
}
@-ms-keyframes #{$name} {
@content;
}
@keyframes #{$name} {
@content;
}
}
@include keyframes(toast-enter-animation) {
0% {
transform: translateX(100%);
}
100% {
transform: translateX(0);
}
}
@include keyframes(toast-leave-animation) {
0% {
transform: translateX(0);
}
100% {
transform: translateX(100%);
}
}
App.js
import React from "react";
import { connect } from "react-redux";
import { addToast } from "./Commons/Toast/redux/action/actionCreators";
const App = (props) => {
const { addToast } = props;
return (
<button
onClick={() =>
addToast({
title: "Toast title",
type: "success",
description: "Toast description",
})
}
>
Click
</button>
);
};
const mapStateToProps = (state) => ({});
const mapDispatchToProps = (dispatch) => ({
addToast: (toast) => dispatch(addToast(toast)),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
我也能够为项目进入和离开进行转换
Toast.js
const Toasts = (props) => {
const { toasts } = props;
return (
<div className="toast-container">
{toasts.map((toast, index) => (
<ToastItem toast={toast} key={index} />
))}
</div>
);
};
Toast.scss
$margin-right: 1rem;
$margin-bottom: 1rem;
.toast-container {
position: absolute;
right: $margin-right;
bottom: $margin-bottom;
z-index: 999;
}
ToastItem.js
const performAsyncTaskAfter = 300;
const ToastItem = (props) => {
const [shouldDelete, setShouldDelete] = useState(true);
const { toast, deleteToast } = props;
const removeToast = () => {
setShouldDelete(true);
setTimeout(() => {
deleteToast(toast.id);
}, performAsyncTaskAfter);
};
useEffect(() => {
setTimeout(() => {
setShouldDelete(false);
}, performAsyncTaskAfter);
}, []);
return (
<div
className={`toast bg-${toast.type} ${!shouldDelete ? `toast-enter` : ""}`}
>
<div className="toast-header">
<p>{toast.title}</p>
<button onClick={removeToast}>X</button>
</div>
{toast.description && (
<div className="toast-description">
<p>{toast.description}</p>
</div>
)}
</div>
);
};
ToastItem.scss
$bg-danger: #dc3545;
$bg-warning: #ffc107;
$bg-info: #0d6efd;
$bg-success: #198754;
.toast {
padding: 0.5rem 1rem;
opacity: 0.9;
color: #fff;
width: 250px;
margin-top: 0.5rem;
transform: translateX(calc(100% + 1rem));
transition: all 0.3s;
&:hover {
opacity: 1;
}
}
.toast-enter {
transform: translateX(0);
}
.toast-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.toast-header button {
background-color: inherit;
outline: none;
border: none;
color: inherit;
&:hover {
cursor: pointer;
}
}
.bg-danger {
background-color: $bg-danger;
}
.bg-warning {
background-color: $bg-warning;
}
.bg-info {
background-color: $bg-info;
}
.bg-success {
background-color: $bg-success;
}