Next.js 构建博客之功能拓展

  1. Next.js 构建博客之资源抓取
  2. Next.js 构建博客之博客搭建
  3. Next.js 构建博客之打包 SSG
  4. Next.js 构建博客之常见问题处理

这是 Next.js 构建博客的第五篇文章,上一篇文章介绍了 Next.js 如何处理常见的问题,这一篇主要介绍给博客进行功能增强。

图片放大缩小

在详情页面会经常遇到图片,很多时候为了考虑排版只会放一个等比例缩小的图片,而不是任由图片展示初始尺寸,这个时候为了为了查看图片就需要考虑功能的增强了。

这里介绍一下怎么来进行添加,不过在添加之前需要想一下,图片放大缩小这个功能,我们需要用服务器渲染还是客户端渲染?

我的建议是客户端渲染即可,因为服务器渲染一方面适合比较通用的部分,另外则是 seo 抓取跟图片本身其实关联不大,默认情况下 html 携带图片 alt 属性就足够了。

博客的渲染使用了 @bytemd/react,看一个官方文档的示例

js 复制代码
import gfm from "@bytemd/plugin-gfm";
import { Editor, Viewer } from "@bytemd/react";

const plugins = [
  gfm(),
  // Add more plugins here
];

const App = () => {
  const [value, setValue] = useState("");

  return (
    <Editor
      value={value}
      plugins={plugins}
      onChange={(v) => {
        setValue(v);
      }}
    />
  );
};

传递 value 属性就得到一个完整的 view,不过这里不太符合我们要求,因为我们需要给 <img /> 添加 onClik 事件,有两种思路可以做到:

  1. 一种是@bytemd/react 自定义插件
  2. 另外一种就是拦截整体整体区域的点击,利用冒泡机制即可。

下面是一个示例,使用第二种方式

js 复制代码
 const [visible, setVisible] = useState(false);
  const [activeIndex, setActiveIndex] = useState(0);
  useEffect(() => {
    const dom = document.querySelector(".markdown-body") as HTMLDivElement;
    const callback = (e: MouseEvent) => {
      const dom = e.target as HTMLImageElement;
      if (!/img/i.test(dom.nodeName)) {
        return;
      }
      const index = imgAll.indexOf(dom.src);
      setActiveIndex(index);
      setVisible(true);
    };
    dom.addEventListener("click", callback);
    return () => {
      dom.removeEventListener("click", callback);
    };
  }, [imgAll]);

之后简单封装一下预览图片组件,这里使用了 react-viewer

js 复制代码
"use client";
import { Dispatch, SetStateAction, useEffect, useMemo } from "react";
import Viewer from "react-viewer";

interface Props {
  imgAll: string[];
  visible: boolean;
  setVisible: Dispatch<SetStateAction<boolean>>;
  activeIndex: number;
}

export function Preview({ visible, imgAll, setVisible, activeIndex }: Props) {
  const images = useMemo(() => {
    return imgAll.map((f) => {
      return {
        src: f,
        alt: f.split("/").at(-1),
      };
    });
  }, [imgAll]);
  // 防止点开抖动
  const id = "article_style";

  useEffect(() => {
    if (!visible) {
      const dom = document.querySelector(`#${id}`);
      if (dom) {
        // 延迟去除,防止抖动
        setTimeout(() => {
          document.head.removeChild(dom);
        }, 500);
      }
      return;
    }
    const { clientWidth } = window.document.documentElement;
    const screenDifference = window.innerWidth - clientWidth;
    const content = `
      html body{
        overflow-Y:hidden;
        ${
          screenDifference > 0 ? `width:calc(100% - ${screenDifference}px)` : ""
        }
      }
  `;
    const style =
      document.querySelector(`#${id}`) || document.createElement("style");
    style.id = id;
    style.innerHTML = content;
    document.head.appendChild(style);
  }, [visible]);

  return (
    <Viewer
      visible={visible}
      activeIndex={activeIndex}
      onClose={() => {
        setVisible(false);
      }}
      onMaskClick={() => {
        setVisible(false);
      }}
      images={images}
    />
  );
}

之后引入

js 复制代码
const Preview = dynamic(() => import("./preview").then((e) => e.Preview), {
  ssr: false,
});

点击量

很多时候需要对文章点击量进行一个整体衡量,包括站点访问量之类的,这里用的是 不蒜子 - 极简网页计数器

首先安装依赖

sh 复制代码
pnpm i busuanzi.pure.js

之后在每次路由发生变化的时候进行监听

js 复制代码
"use client";
import { usePathname } from "next/navigation";
import { fetch } from "busuanzi.pure.js";
import { useUpdateEffect } from "ahooks";

// 给文章添加点击量
export default function Statistics() {
  const pathname = usePathname();
  useUpdateEffect(() => {
    fetch();
  }, [pathname]);

  return null;
}

在 app/page.tsx 页面引入

js 复制代码
const Statistics = dynamicNext(() => import("./statistics"), { ssr: false });

return () => {
  <Statistics></Statistics>;
};

在需要地方引入,例如我需要某一篇文章的访问量,那我就

html 复制代码
<span>
  <i className="qzf qzf-eye" />
  <span id="busuanzi_value_page_pv" suppressHydrationWarning> 0 </span>次
</span>

其他的方式可以看一下文章,最后需要注意一下,需要设置 meta 属性为 no-referrer-when-downgrade,具体原因看不蒜子在 Chrome 85 版本后所有页面统计是同一个数据

js 复制代码
export const metadata: Metadata = {
  referrer: "no-referrer-when-downgrade",
};

添加收录

这里以 Google 为例,可以访问此网站,根据示例一步步来。

然后在 app/layout.tsx 中添加下面这样的代码

js 复制代码
  return (
    <html lang="zh">
        <meta
          name="google-site-verification"
          content="4FVbyJeMZIl9kKhdo9gaJLqZviP6Z5En9GbS5VD8g6w"
        />
      </head>
      <body>

      </body>
    </html>
  );

给代码添加复制和在线运行功能

这块因为这段时间心情很糟糕,代码并没有写完,所以下面全部都是伪代码形式。

因为渲染 md 用的组件是 @bytemd/react,它其实是支持插件拓展的,放一张官方的图

有两个步骤可以完成添加的功能

  • The HTML AST could be manipulated by several rehype plugins
  • Some extra DOM manipulation after the HTML being rendered

最后一种对 ssr 没有帮助,相当于客户端渲染了,不过对 dom 添加之类的操作十分方便我们做一些定制,例如添加复制,我们可以写一个 copy 组件,然后在 dom 元素出现的时候直接使用 react-dom render() 指定元素就下可以了。

结合一个官方给出的示例来进行看下

js 复制代码
export default function mathPlugin(): BytemdPlugin {
  return {
    remark: (processor) => processor.use(remarkMath),
+   viewerEffect({ markdownBody }) {
+     const renderMath = async (selector: string, displayMode: boolean) => {
+       const katex = await import('katex').then((m) => m.default)
+
+       const els = markdownBody.querySelectorAll<HTMLElement>(selector)
+       els.forEach((el) => {
+         katex.render(el.innerText, el, { displayMode })
+       })
+     }
+
+     renderMath('.math.math-inline', false)
+     renderMath('.math.math-display', true)
+   },
  }
}

相当于就是操作 dom 元素了。

最后

如果文章有错误之类的欢迎指出,顺便下一篇文章就是收尾了,主要介绍使用 Github Actions 来完成自动发布,在使用的时候只需要监听 issues 的变化就行。

相关推荐
彭世瑜3 分钟前
ts: TypeScript跳过检查/忽略类型检查
前端·javascript·typescript
FØund4044 分钟前
antd form.setFieldsValue问题总结
前端·react.js·typescript·html
Backstroke fish4 分钟前
Token刷新机制
前端·javascript·vue.js·typescript·vue
小五Five5 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
小曲程序5 分钟前
vue3 封装request请求
java·前端·typescript·vue
临枫5416 分钟前
Nuxt3封装网络请求 useFetch & $fetch
前端·javascript·vue.js·typescript
前端每日三省7 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
小刺猬_9858 分钟前
(超详细)数组方法 ——— splice( )
前端·javascript·typescript
渊兮兮9 分钟前
Vue3 + TypeScript +动画,实现动态登陆页面
前端·javascript·css·typescript·动画
鑫宝Code9 分钟前
【TS】TypeScript中的接口(Interface):对象类型的强大工具
前端·javascript·typescript