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 的变化就行。

相关推荐
伍哥的传说1 小时前
鸿蒙系统(HarmonyOS)应用开发之手势锁屏密码锁(PatternLock)
前端·华为·前端框架·harmonyos·鸿蒙
yugi9878381 小时前
前端跨域问题解决Access to XMLHttpRequest at xxx from has been blocked by CORS policy
前端
浪裡遊1 小时前
Sass详解:功能特性、常用方法与最佳实践
开发语言·前端·javascript·css·vue.js·rust·sass
旧曲重听12 小时前
最快实现的前端灰度方案
前端·程序人生·状态模式
默默coding的程序猿2 小时前
3.前端和后端参数不一致,后端接不到数据的解决方案
java·前端·spring·ssm·springboot·idea·springcloud
夏梦春蝉3 小时前
ES6从入门到精通:常用知识点
前端·javascript·es6
马特说3 小时前
React金融数据分析应用性能优化实战:借助AI辅助解决18万数据量栈溢出Bug
react.js·金融·数据分析
归于尽3 小时前
useEffect玩转React Hooks生命周期
前端·react.js
G等你下课3 小时前
React useEffect 详解与运用
前端·react.js
我想说一句3 小时前
当饼干遇上代码:一场HTTP与Cookie的奇幻漂流 🍪🌊
前端·javascript