diff --git a/src/components/AudioPlayer/Player.tsx b/src/components/AudioPlayer/Player.tsx
index 1a1247d..98db5ba 100644
--- a/src/components/AudioPlayer/Player.tsx
+++ b/src/components/AudioPlayer/Player.tsx
@@ -3,6 +3,8 @@ import { useState } from 'react';
import Image from 'next/image';
+import { AutoScrollContainer } from '@/components';
+
export default function AudioPlayer({ className }: { className?: string }) {
const [curTime, setCurTime] = useState('00:00');
const [totalTime, setTotalTime] = useState('00:00');
@@ -16,14 +18,12 @@ export default function AudioPlayer({ className }: { className?: string }) {
{/* title & author */}
- {/* 改成滚动的 */}
-
- {'Ferrum Aeternum'}
-
- {/* 改成滚动的 */}
-
- {'Ensiferum/mmmmsa'}
-
+
+
+
{'Ferrum Aeternumaaaaaaaaaaa'}
+
{'Ensiferum/mmmmsa'}
+
+
{/* progress bar */}
diff --git a/src/components/common/AutoScrollContainer.tsx b/src/components/common/AutoScrollContainer.tsx
new file mode 100644
index 0000000..0d01241
--- /dev/null
+++ b/src/components/common/AutoScrollContainer.tsx
@@ -0,0 +1,90 @@
+'use client';
+
+/**
+ * 滚动容器
+ * TODO: 自定义滚动速度
+ */
+
+import React, { useRef, useEffect, useState } from 'react';
+
+interface TextScrollProps {
+ hover?: boolean;
+ speed?: number;
+ width: string;
+ className?: string;
+ children: React.ReactNode;
+}
+
+const TextScroll: React.FC = ({
+ width = '100px',
+ className = '',
+ children = '',
+ hover = false,
+ speed = 30,
+}) => {
+ const containerRef = useRef(null);
+ const childrenRef = useRef(null);
+ const [isOverflowing, setisOverflowing] = useState(false); // 子元素宽度是否溢出
+ const [isHovered, setIsHovered] = useState(false); // 鼠标是否在 hover 状态
+ const [animation, setAnimation] = useState(''); // 滚动动画
+
+ const handleMouseMove = (isMouseInside: boolean) => {
+ if (!hover) return; // 未传入 'hover = true' 时不响应鼠标事件
+ if (!childrenRef.current) return; // 获取不到 children 时不响应
+ if (isMouseInside && isHovered) return; // 鼠标已经进入时不响应
+
+ if (isMouseInside) {
+ childrenRef.current.style.animation = animation;
+ } else {
+ childrenRef.current.style.animation = '';
+ childrenRef.current.style.transform = 'translateX(0)';
+ }
+
+ setIsHovered(isMouseInside);
+ };
+
+ useEffect(() => {
+ const containerWidth = containerRef.current?.offsetWidth;
+ const childrenWidth = childrenRef.current?.offsetWidth;
+
+ if (containerWidth && childrenWidth && containerWidth < childrenWidth) {
+ setisOverflowing(true);
+ setAnimation(`scroll ${childrenWidth / speed}s linear infinite`);
+ if (!hover) childrenRef.current.style.animation = animation;
+ }
+ }, [children, speed, hover, animation]); // 当children变化时重新计算
+
+ return (
+ handleMouseMove(true)}
+ onMouseLeave={() => handleMouseMove(false)}
+ >
+
+ {children}
+ {isOverflowing && (
+ <>
+
+ {children}
+
+ >
+ )}
+
+
+
+
+ );
+};
+
+export default TextScroll;
diff --git a/src/components/index.ts b/src/components/index.ts
index 7b1519a..89f4075 100644
--- a/src/components/index.ts
+++ b/src/components/index.ts
@@ -19,6 +19,7 @@ export { default as HotJournalList } from './Journal/HotJournalList';
// Common
export { default as Input } from './common/Input';
export { default as Button } from './common/Button';
+export { default as AutoScrollContainer } from './common/AutoScrollContainer';
// Audio Player
export { default as PlayerBar } from './AudioPlayer/PlayerBar';