feat: animated expansion of md-link
This commit is contained in:
parent
2794b21a41
commit
31b6b57ec8
|
|
@ -7,6 +7,7 @@ export interface ArticleProps {
|
||||||
section?: string; // 指定要显示的标题(不含 #)
|
section?: string; // 指定要显示的标题(不含 #)
|
||||||
onLoaded?: () => void;
|
onLoaded?: () => void;
|
||||||
onError?: (error: Error) => void;
|
onError?: (error: Error) => void;
|
||||||
|
class?: string; // 额外的 class 用于样式控制
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchArticleContent(params: { src: string; section?: string }): Promise<string> {
|
async function fetchArticleContent(params: { src: string; section?: string }): Promise<string> {
|
||||||
|
|
@ -39,7 +40,7 @@ export const Article: Component<ArticleProps> = (props) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article ref={articleRef} class="prose" data-src={props.src}>
|
<article ref={articleRef} class={`prose ${props.class || ''}`} data-src={props.src}>
|
||||||
<Show when={content.loading}>
|
<Show when={content.loading}>
|
||||||
<div class="text-gray-500">加载中...</div>
|
<div class="text-gray-500">加载中...</div>
|
||||||
</Show>
|
</Show>
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,10 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
noShadowDOM();
|
noShadowDOM();
|
||||||
|
|
||||||
const [showArticle, setShowArticle] = createSignal(false);
|
const [showArticle, setShowArticle] = createSignal(false);
|
||||||
|
const [expanded, setExpanded] = createSignal(false);
|
||||||
let articleContainer: HTMLDivElement | undefined;
|
let articleContainer: HTMLDivElement | undefined;
|
||||||
let disposeArticle: (() => void) | null = null;
|
let disposeArticle: (() => void) | null = null;
|
||||||
|
let articleElement: HTMLElement | undefined;
|
||||||
|
|
||||||
// 从 element 的 textContent 获取链接目标(支持 path#section 语法)
|
// 从 element 的 textContent 获取链接目标(支持 path#section 语法)
|
||||||
const rawLinkSrc = element?.textContent?.trim() || "";
|
const rawLinkSrc = element?.textContent?.trim() || "";
|
||||||
|
|
@ -38,14 +40,20 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
if (!parentP) return;
|
if (!parentP) return;
|
||||||
|
|
||||||
if (showArticle()) {
|
if (showArticle()) {
|
||||||
// 隐藏文章
|
// 隐藏文章 - 先折叠再移除
|
||||||
|
setExpanded(false);
|
||||||
if (articleContainer) {
|
if (articleContainer) {
|
||||||
if (disposeArticle) {
|
articleContainer.style.height = '0';
|
||||||
disposeArticle();
|
articleContainer.style.opacity = '0';
|
||||||
disposeArticle = null;
|
setTimeout(() => {
|
||||||
}
|
if (disposeArticle) {
|
||||||
articleContainer.remove();
|
disposeArticle();
|
||||||
articleContainer = undefined;
|
disposeArticle = null;
|
||||||
|
}
|
||||||
|
articleContainer?.remove();
|
||||||
|
articleContainer = undefined;
|
||||||
|
articleElement = undefined;
|
||||||
|
}, 300);
|
||||||
}
|
}
|
||||||
setShowArticle(false);
|
setShowArticle(false);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -53,6 +61,10 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
articleContainer = document.createElement("div");
|
articleContainer = document.createElement("div");
|
||||||
articleContainer.classList.add("md-link-article");
|
articleContainer.classList.add("md-link-article");
|
||||||
articleContainer.classList.add("ml-4", "border-l-2", "border-gray-200", "pl-4");
|
articleContainer.classList.add("ml-4", "border-l-2", "border-gray-200", "pl-4");
|
||||||
|
articleContainer.style.height = '0';
|
||||||
|
articleContainer.style.opacity = '0';
|
||||||
|
articleContainer.style.overflow = 'hidden';
|
||||||
|
articleContainer.style.transition = 'height 0.3s ease, opacity 0.3s ease';
|
||||||
parentP.after(articleContainer);
|
parentP.after(articleContainer);
|
||||||
|
|
||||||
// 渲染 Article 组件
|
// 渲染 Article 组件
|
||||||
|
|
@ -60,7 +72,19 @@ customElement("md-link", {}, (props, { element }) => {
|
||||||
<Article
|
<Article
|
||||||
src={linkSrc}
|
src={linkSrc}
|
||||||
section={section}
|
section={section}
|
||||||
onLoaded={() => console.log("Article loaded:", linkSrc)}
|
class="article-animate"
|
||||||
|
onLoaded={() => {
|
||||||
|
// 内容加载完成后,获取实际高度并展开
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
articleElement = articleContainer?.querySelector('article[data-src]');
|
||||||
|
if (articleElement) {
|
||||||
|
const height = articleElement.scrollHeight;
|
||||||
|
articleContainer!.style.height = `${height}px`;
|
||||||
|
articleContainer!.style.opacity = '1';
|
||||||
|
setExpanded(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}}
|
||||||
onError={(err) => console.error("Article error:", err)}
|
onError={(err) => console.error("Article error:", err)}
|
||||||
/>
|
/>
|
||||||
), articleContainer);
|
), articleContainer);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue