因为喜欢写一些技术文章,因此代码在文章中的比例还是比较大的。给高亮代码弄个良好的外观很有必要。

一、哪些外观

1.顶部 banner 修饰

给多行代码顶部弄个 banner,是很多高亮代码都提供了的美学体现

2.代码语言

能让人一眼就了解代码所使用的语言也是一个不错的选择

3.代码折叠

很早之前就因为看到 csdn 上面的优雅的代码折叠功能而心动,于是实现了一下。但那时因为没有良师或者说是 AI 的帮助,思路不够开阔,只做了个默认全部展开,然后再通过按钮实现折叠。但初始那长长的代码总是影响了我的心情。

前段时间看到 小十 的博客及那个不错的代码折叠效果,又勾起了我优化的欲望,加上是因为现在 AI 解惑的能力确实很强,而且能够提供良好的思路,于是,我再次进行了修改,终于实现了良好的代码折叠功能。

  1. 默认对超过 5 行的代码折叠
  2. 默认可展开的代码添上遮罩层

二、代码实现

依托 highlight.js 实现。

  • 在 pre 之前添加一个 banner,左侧添加伪元素(:before)显示 3 个不同颜色的圆点,右侧显示代码语言
  • 在 code 下方,添加 1 个遮罩 div 及一个折叠按钮,按钮在折叠时不占空间(absolute),展开时则位于 code 下面

需要用到的 css 代码示例

/* 头部 banner */
.code-header {
  position: relative;
  background-color: rgba(200, 200, 200, 0.5);
  height: 2rem;
  padding-left: 10rem;
  padding-right: 1rem;
  margin-top: 0.75rem;
  margin-bottom: 0px;
  border-top-left-radius: 0.35rem;
  border-top-right-radius: 0.35rem;
  display: flex;
  justify-content: end;
  align-items: center;
}

.code-header:before {
  content: "";
  position: absolute;
  border-radius: 50%;
  background: #fc625d;
  width: 0.8rem;
  height: 0.8rem;
  left: 0.7rem;
  top: 0.7rem;
  box-shadow: 1.4rem 0 #fdbc40, 2.8rem 0 #35cd4b;
}

/* pre标签 */
pre {
  margin: 0;
  margin-bottom: 0.75rem;
  position: relative;
  border-bottom-left-radius: 5px;
  border-bottom-right-radius: 5px;
  letter-spacing: 0;
  overflow: hidden;
}

/* 折叠高度 */
pre.collapsed {
  max-height: 10rem;
}

/* pre code */
code.hljs {
  position: static;
  width: auto;
  background-color: transparent;
  font-size: 0.9em;
  overflow-x: auto;
}

/* 遮罩层 */
.fade-overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  height: 2rem;
  background: -webkit-linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.5));
  background: -moz-linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.5));
  background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.5));
}

/* 折叠按钮 */
.troggle-btn {
  width: 100%;
  display: flex;
  align-items: end;
  justify-content: center;
  cursor: pointer;
}

.troggle-absolute {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
}

javascript 实现代码

// pre 操作
$(".post-content pre").each(function () {
  const $pre = $(this);
  const language = getLanguageFromPre(this); //代码语言获取方法

  // 代码头部
  const $header = $("<div>", {
    class: "code-header",
    html: `<span class="language">${language || "Code"}</span>`,
  });

  // 遮罩层
  const $overlay = $("<div>", {
    class: "fade-overlay",
  });

  // 折叠按钮
  const $trogglebtn = $("<button>", {
    class: "troggle-btn troggle-absolute",
    html: `<i class="ri-arrow-down-double-line"></i>展开<i class="ri-arrow-down-double-line"></i>`,
  });

  // 添加头部
  $pre.before($header);
  //添加遮罩层及折叠按钮
  $pre.append([$overlay, $trogglebtn]);

  // 检查代码行数并添加折叠功能
  const lineCount = $pre.text().split("\n").length;
  if (lineCount > 5) {
    $pre.addClass("collapsed");
    $overlay.show();
    $trogglebtn.show();
  } else {
    $overlay.hide();
    $trogglebtn.hide();
  }

  // 绑定折叠/展开事件
  $trogglebtn.on("click", function () {
    const $btn = $(this);
    const $codeContainer = $btn.closest("pre"); //查最近的祖先元素 pre
    const $overlay = $codeContainer.find(".fade-overlay"); //查找 pre 下面的遮罩层
    const $code = $codeContainer.find("code.hljs"); //查找代码体
    if ($codeContainer.hasClass("collapsed")) {
      // 展开
      $codeContainer.removeClass("collapsed");
      $overlay.hide();
      $btn.html('<i class="ri-arrow-up-double-line"></i>折叠<i class="ri-arrow-up-double-line"></i>'); //用到了图标库样式
      $btn.removeClass("troggle-absolute");
    } else {
      // 折叠
      $codeContainer.addClass("collapsed");
      $overlay.show();
      $btn.html('<i class="ri-arrow-down-double-line"></i>展开<i class="ri-arrow-down-double-line"></i>');
      $btn.addClass("troggle-absolute");
    }
  });
});

// 从pre标签下的code标签类名中提取语言代码
function getLanguageFromPre(preElement) {
  const $pre = $(preElement);
  const $code = $pre.find("code");

  if ($code.length === 0) {
    return null;
  }

  const classNames = $code.attr("class") || "";

  // 查找 lang-xxx
  const langMatch = classNames.match(/\blang-(\w+)\b/);
  if (langMatch) return langMatch[1];

  // 查找 language-xxx
  const languageMatch = classNames.match(/\blanguage-(\w+)\b/);
  if (languageMatch) return languageMatch[1];

  return null;
}