feat(directory): add a scroll to top button
This commit is contained in:
@@ -24,29 +24,38 @@ export default function Dropdown(props) {
|
|||||||
<ul className={classes.menu} style={props.activeColor}>
|
<ul className={classes.menu} style={props.activeColor}>
|
||||||
{
|
{
|
||||||
props.menu.map((item) => {
|
props.menu.map((item) => {
|
||||||
if (item.type === 'group') {
|
switch (item.type) {
|
||||||
return (
|
case 'date': {
|
||||||
<section
|
return (
|
||||||
key={item.name}
|
<section
|
||||||
className={classes.group}
|
key={item.name}
|
||||||
>
|
className={classes.date}
|
||||||
<section className={classes.line} />
|
>
|
||||||
<section className={classes.text}>{item.name}</section>
|
<section className={classes.line} />
|
||||||
</section>
|
<section className={classes.text}>{item.name}</section>
|
||||||
)
|
</section>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
case 'custom': {
|
||||||
|
return (
|
||||||
|
item.component
|
||||||
|
)
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
return (
|
||||||
|
<li
|
||||||
|
key={item.name}
|
||||||
|
className={`${classes.item} ${item.name === props.text || (props.activeRule && props.activeRule(item)) ? classes.active : ''}`}
|
||||||
|
onClick={() => {
|
||||||
|
props.onClick(item)
|
||||||
|
toggleDropdown()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<section className={classes.text}>{item.name}</section>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return (
|
|
||||||
<li
|
|
||||||
key={item.name}
|
|
||||||
className={`${classes.item} ${item.name === props.text || (props.activeRule && props.activeRule(item)) ? classes.active : ''}`}
|
|
||||||
onClick={() => {
|
|
||||||
props.onClick(item)
|
|
||||||
toggleDropdown()
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<section className={classes.text}>{item.name}</section>
|
|
||||||
</li>
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -56,7 +56,7 @@
|
|||||||
color: var(--link-highlight-color);
|
color: var(--link-highlight-color);
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
|
|
||||||
.group {
|
.date {
|
||||||
font-family: "Bender";
|
font-family: "Bender";
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
font-size: 24px;
|
font-size: 24px;
|
||||||
|
|||||||
84
directory/src/component/scss/totop_button.module.scss
Normal file
84
directory/src/component/scss/totop_button.module.scss
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
.totop-button {
|
||||||
|
position: fixed;
|
||||||
|
user-select: none;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
height: 2rem;
|
||||||
|
right: 2rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
justify-content: flex-start;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity cubic-bezier(0.65, 0.05, 0.36, 1) 0.3s;
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 0.3rem;
|
||||||
|
background-color: var(--text-color-full);
|
||||||
|
will-change: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(1) {
|
||||||
|
transform: rotateZ(45deg) scaleX(0.5) translateX(45%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(2) {
|
||||||
|
transform: rotateZ(-45deg) scaleX(0.5) translateX(-45%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(3), .bar:nth-child(4) {
|
||||||
|
transform: translateY(450%) rotateZ(90deg) scaleX(0.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.clicked {
|
||||||
|
.bar:nth-child(1) {
|
||||||
|
animation: transform-1 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(2) {
|
||||||
|
animation: transform-2 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(3) {
|
||||||
|
animation: transform-3 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bar:nth-child(4) {
|
||||||
|
animation: transform-4 2s cubic-bezier(0.65, 0.05, 0.36, 1) infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes transform-1 {
|
||||||
|
50% {
|
||||||
|
transform: translateY(450%) rotateZ(-90deg) scaleX(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes transform-2 {
|
||||||
|
50% {
|
||||||
|
transform: translateY(450%) rotateZ(90deg) scaleX(0.5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@keyframes transform-3 {
|
||||||
|
50% {
|
||||||
|
transform: rotateZ(225deg) scaleX(0.5) translateX(-45%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes transform-4 {
|
||||||
|
50% {
|
||||||
|
transform: rotateZ(-45deg) scaleX(0.5) translateX(-45%);
|
||||||
|
}
|
||||||
|
}
|
||||||
67
directory/src/component/totop_button.jsx
Normal file
67
directory/src/component/totop_button.jsx
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
import React, {
|
||||||
|
useEffect,
|
||||||
|
useState,
|
||||||
|
useCallback
|
||||||
|
} from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import classes from './scss/totop_button.module.scss'
|
||||||
|
|
||||||
|
export default function ToTopButton(props) {
|
||||||
|
const [hidden, setHidden] = useState(true)
|
||||||
|
const [clicked, setClicked] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const handleButton = () => {
|
||||||
|
const scrollBarPos = window.pageYOffset || 0;
|
||||||
|
setHidden(!(scrollBarPos > 100))
|
||||||
|
}
|
||||||
|
window.addEventListener('scroll', handleButton)
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener('scroll', handleButton)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const smoothScroll = useCallback(
|
||||||
|
(target) => {
|
||||||
|
const targetElement = document.querySelector(target);
|
||||||
|
const targetPosition = targetElement.getBoundingClientRect().top + window.pageYOffset;
|
||||||
|
const startPosition = window.pageYOffset;
|
||||||
|
const distance = targetPosition - startPosition;
|
||||||
|
const duration = 1000;
|
||||||
|
let start = null;
|
||||||
|
window.requestAnimationFrame(step);
|
||||||
|
function step(timestamp) {
|
||||||
|
if (!start) start = timestamp;
|
||||||
|
const progress = timestamp - start;
|
||||||
|
window.scrollTo(0, easeInOutCubic(progress, startPosition, distance, duration));
|
||||||
|
if (progress < duration) window.requestAnimationFrame(step);
|
||||||
|
}
|
||||||
|
function easeInOutCubic(t, b, c, d) {
|
||||||
|
t /= d / 2;
|
||||||
|
if (t < 1) return c / 2 * t * t * t + b;
|
||||||
|
t -= 2;
|
||||||
|
return c / 2 * (t * t * t + 2) + b;
|
||||||
|
}
|
||||||
|
setClicked(true)
|
||||||
|
setTimeout(() => {
|
||||||
|
setClicked(false)
|
||||||
|
}, duration)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<section className={`${classes['totop-button']} ${clicked ? classes.clicked : ''} ${hidden ? '' : classes.show} ${props.className ? props.className : ''}`}
|
||||||
|
onClick={() => { smoothScroll("#root") }}
|
||||||
|
>
|
||||||
|
<section className={classes.bar}></section>
|
||||||
|
<section className={classes.bar}></section>
|
||||||
|
<section className={classes.bar}></section>
|
||||||
|
<section className={classes.bar}></section>
|
||||||
|
</section>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ToTopButton.propTypes = {
|
||||||
|
onClick: PropTypes.func,
|
||||||
|
className: PropTypes.string,
|
||||||
|
};
|
||||||
@@ -31,6 +31,7 @@ import Popup from '@/component/popup';
|
|||||||
import ReturnButton from '@/component/return_button';
|
import ReturnButton from '@/component/return_button';
|
||||||
import Border from '@/component/border';
|
import Border from '@/component/border';
|
||||||
import CharIcon from '@/component/char_icon';
|
import CharIcon from '@/component/char_icon';
|
||||||
|
import ToTopButton from '@/component/totop_button';
|
||||||
|
|
||||||
const currentYear = new Date().getFullYear()
|
const currentYear = new Date().getFullYear()
|
||||||
|
|
||||||
@@ -132,6 +133,7 @@ export default function Root() {
|
|||||||
</section>
|
</section>
|
||||||
</section>
|
</section>
|
||||||
<HeaderButton />
|
<HeaderButton />
|
||||||
|
<ToTopButton />
|
||||||
<Outlet />
|
<Outlet />
|
||||||
<ScrollRestoration />
|
<ScrollRestoration />
|
||||||
</main>
|
</main>
|
||||||
@@ -305,7 +307,7 @@ function HeaderButton() {
|
|||||||
list.push({
|
list.push({
|
||||||
name: key,
|
name: key,
|
||||||
value: null,
|
value: null,
|
||||||
type: "group",
|
type: "date",
|
||||||
})
|
})
|
||||||
value.forEach((item) => {
|
value.forEach((item) => {
|
||||||
list.push({
|
list.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user