JavaScript >> Javascript 文檔 >  >> JavaScript

如何使用 JavaScript 將錨標記動態添加到 HTML

如何動態生成錨鏈接並將其註入 HTML 以改善博客或基於內容的應用的 UX(用戶體驗)。

SEO 的很大一部分是提高您網站的可索引性並確保您的內容滿足用戶查詢的需求。您可以添加的一點 UX(用戶體驗)——特別是如果你正在創作像博客這樣的長篇內容——是為內容的不同部分提供錨鏈接。

手工操作是一件苦差事,所以在本教程中,我們將學習如何自動遍歷一些 HTML,找到所有的 h1-h6 標籤,並自動更新它們以包含一個錨鏈接(完成一個 slugified 版本其文本)。

開始使用

首先,我們將依靠 CheatCode Next.js Boilerplate 為我們提供一個良好的起點。首先,克隆一個樣板的副本:

終端

git clone https://github.com/cheatcode/nextjs-boilerplate

然後,安裝樣板的依賴:

終端

cd nextjs-boilerplate && npm install

安裝這些依賴項後,安裝我們稍後將在教程中使用的以下依賴項:

終端

npm i cheerio commonmark speakingurl

安裝完這些後,繼續並啟動樣板:

終端

npm run dev

編寫錨鏈接器

在我們真正“看到”屏幕上的任何東西之前,我們將專注於我們需要幫助我們自動將錨鏈接添加到我們的內容的核心功能。首先,讓我們在 /lib/anchorLinker.js 處設置一個函數 我們的代碼所在的位置:

/lib/anchorLinker.js

const anchorLinker = (content = "") => {
  // Our automatic anchor linking will go here.
};

export default anchorLinker;

簡單的。在這裡,我們只是為我們的函數創建一個骨架,添加一個 content 我們期望是一個字符串的參數。 content = "" 這裡的語法是說“如果沒有為 content 傳遞值 , 為其分配一個空字符串的默認值。”

/lib/anchorLinker.js

import isClient from "./isClient";

const anchorLinker = (content = "") => {
  if (isClient) {
    // Client-side linking will go here.
  }

  // Server-side linking will go here.
};

export default anchorLinker;

接下來,我們介紹了一個 if 語句,檢查是否 isClient 為真(isClient 被添加為頂部的導入,是一個自動包含在 /lib/isClient.js 樣板文件中的函數 )。我們在這裡添加了這個,因為即使我們正在使用僅前端的樣板,Next.js(樣板構建在其之上的框架)也具有服務器端呈現功能,可以為搜索引擎生成 HTML。

它通過一個名為 getServerSideProps() 的函數來執行此操作 .此函數在初始請求進入基於 Next.js 的應用程序時運行。在該請求在瀏覽器中收到 HTML 形式的響應之前,首先 Next.js 調用 getServerSideProps()之前幫助數據獲取和其他服務器端任務 將 HTML 返回到請求。

由於此函數在服務器上下文中運行,因此某些瀏覽器級別的 API(例如 DOM 操作方法)不可用。所以,當這段代碼在 that 中運行時 上下文,它會引發錯誤。為了解決這個問題,我們將在這裡編寫兩組代碼:錨鏈接器的客戶端實現和錨鏈接器的服務器端實現。

添加客戶端錨鏈接

對於客戶端,我們可以完全訪問瀏覽器 DOM 操作 API,因此我們不需要引入任何特殊的依賴項或代碼:

/lib/anchorLinker.js

import isClient from "./isClient";
import parseMarkdown from "./parseMarkdown";

const anchorLinker = (content = "") => {
  if (isClient) {
    const html = document.createElement("div");
    html.innerHTML = parseMarkdown(content);
  }

  // Server-side linking will go here.
};

export default anchorLinker;

首先,為了隔離我們的content生成的HTML 字符串,我們使用 document.createElement() 創建 <div></div> 的方法 元素(在內存中,不渲染到屏幕上)。接下來,我們填充 <div></div> 調用 parseMarkdown() 的結果 ,傳入我們的內容。

真快,讓我們添加這個函數,這樣我們就可以完成導入:

/lib/parseMarkdown.js

import { Parser, HtmlRenderer } from "commonmark";

const parseMarkdown = (markdown = "", options) => {
  if (markdown) {
    const reader = new Parser();
    const writer = options ? new HtmlRenderer(options) : new HtmlRenderer();
    const parsed = reader.parse(markdown);
    return writer.render(parsed);
  }

  return "";
};

export default parseMarkdown;

Markdown 是一種使用特殊語法從文本文件生成 HTML 的簡寫語言。所以我們可以跳過為我們的測試編寫一堆 HTML 標籤,我們將使用 Markdown 為我們自動生成 HTML。這裡,parseMarkdown() 是一個環繞 commonmark 的函數 圖書館。 Commonmark 是一個 Markdown 解析器,它根據 Markdown 規範接收一個字符串並將其轉換為 HTML。

這裡的細節是有限的,因為這只是遵循 commonmark 中的說明 有關如何使用解析器的文檔。為了使用它,我們創建了一個 Parser 的實例 然後創建 HtmlRenderer 的實例 .在這裡,我們有條件地調用 new HtmlRenderer 基於是否將值傳遞給第二個 options parseMarkdown 的參數 函數(如果需要,這些是 commonmark 的選項)。

使用我們的 HtmlRenderer 配置並存儲在 writer 變量,接下來,我們解析我們的 markdown 字符串到虛擬 DOM(文檔對像模型),然後使用 writer.render() 將該 DOM 轉換為 HTML 字符串。

/lib/anchorLinker.js

import cheerio from "cheerio";
import isClient from "./isClient";
import parseMarkdown from "./parseMarkdown";
import getSlug from "./getSlug";

const anchorLinker = (content = "") => {
  if (isClient) {
    const html = document.createElement("div");
    html.innerHTML = parseMarkdown(content);

    const hTags = html.querySelectorAll("h1, h2, h3, h4, h5, h6");

    hTags.forEach((hTag) => {
      const tagContent = hTag.innerHTML;
      const tagSlug = getSlug(tagContent);

      hTag.innerHTML = `<a class="anchor-link" href="#${tagSlug}"><i class="fas fa-link"></i></a> ${tagContent}`;
      hTag.setAttribute("id", tagSlug);
    });

    return html.innerHTML;
  }
};

export default anchorLinker;

隨著我們的 Markdown 被解析為 HTML,現在我們可以進入本教程的內容了。回到我們的 /lib/anchorLinker.js 文件,我們擴展了 if (isClient) 我們的 anchorLinker() 塊 啟動錨鏈接過程的函數。

為了自動鏈接我們內容中的所有 h1-h6 標籤,我們需要從 <div></div> 中檢索這些元素 我們之前創建,然後用 parseMarkdown() 中將 Markdown 解析為 HTML 的結果填充它 .

使用 html.querySelectorAll("h1, h2, h3, h4, h5, h6") ,我們說“去把這個 HTML 中的所有 h1-h6 標籤都給我們。”這給了我們一個包含所有 h1-h6 標籤的 JavaScript DOM 節點列表。有了這個,接下來,我們調用 hTags.forEach() 在每個發現的 h1-h6 標籤上運行一個循環。

在我們的 forEach() 的回調中 我們會做必要的工作來“自動鏈接”我們的標籤。為此,首先,我們抓取標籤的未修改內容(這是標籤中的文本,例如,<h1>This is an h1 anchor</h1> 中的“這是一個 h1 錨點” ) 通過 hTag.innerHTML hTaghTags 中的當前標記 我們正在循環的數組。

有了這些內容,接下來我們介紹一個新函數getSlug() 幫助我們創建標籤內容的 slugified、URL 安全版本,例如 this-is-an-h1-anchor .讓我們快速看一下這個函數並討論它是如何工作的:

/lib/getSlug.js

import speakingUrl from "speakingurl";

const getSlug = (string = "") => {
  return speakingUrl(string, {
    separator: "-",
    custom: { "'": "" },
  });
};

export default getSlug;

在這個文件中,我們所做的只是圍繞 speakingurl 創建一個包裝函數 我們在教程開始時安裝的依賴項。這裡,speakingUrl() 是一個接收 string 的函數 並將其轉換為 a-hyphenated-slug-like-this .就是這樣!

/lib/anchorLinker.js

import cheerio from "cheerio";
import isClient from "./isClient";
import parseMarkdown from "./parseMarkdown";
import getSlug from "./getSlug";

const anchorLinker = (content = "") => {
  if (isClient) {
    const html = document.createElement("div");
    html.innerHTML = parseMarkdown(content);

    const hTags = html.querySelectorAll("h1, h2, h3, h4, h5, h6");

    hTags.forEach((hTag) => {
      const tagContent = hTag.innerHTML;
      const tagSlug = getSlug(tagContent);

      hTag.innerHTML = `<a class="anchor-link" href="#${tagSlug}"><i class="fas fa-link"></i></a> ${tagContent}`;
      hTag.setAttribute("id", tagSlug);
    });

    return html.innerHTML;
  }
};

export default anchorLinker;

回到我們的 /lib/anchorLinker.js 文件,現在我們準備創建我們的錨鏈接。在這裡,我們取當前的 hTag 我們正在循環並修改它的 innerHTML (意思是的內容 hTag ,但不是 hTag 本身)包含一個 <a></a> 包裹在鏈接圖標周圍的標記(取自我們正在使用的 Next.js 樣板中包含的 Font Awesome 庫)。

除此之外,如果我們仔細觀察,我們會注意到對於 <a></a> 我們要添加的標籤,我們設置 href 屬性等於 #${tagSlug} .這個很重要。這裡,# 其中一部分是告訴網絡瀏覽器以下文本代表 id 頁面上的一個元素。當在 URL 欄中輸入時,這將觸發瀏覽器查找具有該 id 的元素 在頁面上並將用戶向下滾動到它。這就是為什麼它被稱為“錨”鏈接的原因:它是 anchoring 內容中特定點的 URL。

設置 id ,我們使用 hTag.setAttribute() 設置 idhTag 我們目前正在循環。我們在這裡設置(而不是在 <a></a> 標籤),因為我們試圖將用戶直接錨定到內容,而不是鏈接本身。

在此之後,我們完成了我們的 if (isClient) 通過返回 html.innerHTML 來阻止 ,或者,我們的 content 轉換為 HTML 並更新為包含我們的錨標記(我們將在屏幕上呈現的內容)。

添加服務器端錨鏈接

在我們使用它之前,回想一下之前我們提到需要 處理此鏈接以進行服務器端渲染。這裡的概念是相同的,但我們將使用的方法不同(同樣,服務器端環境 可以訪問 DOM 操作 API,例如 document.querySelectorAll()hTag.setAttribute() )。

為了幫助我們,我們將依賴 cheerio 我們在本教程開始時安裝的依賴項。 Cheerio 是一個服務器端、Node.js 友好的 DOM 操作庫。既然我們已經了解了這裡的機制,讓我們添加我們需要的代碼來完成我們剛剛使用 cheerio 所做的事情 並逐步完成:

/lib/anchorLinker.js

import cheerio from "cheerio";
import isClient from "./isClient";
import parseMarkdown from "./parseMarkdown";
import getSlug from "./getSlug";

const anchorLinker = (content = "") => {
  if (isClient) {
    [...]

    return html.innerHTML;
  }

  const $ = cheerio.load("<div></div>");
  $("div").html(content);

  const hTags = $("body").find("h1, h2, h3, h4, h5, h6");

  hTags.each(function () {
    const tagContent = $(this).text();
    const tagSlug = getSlug(tagContent);

    $(this).html(
      `<a class="anchor-link" href="#${tagSlug}"><i class="fas fa-link"></i></a> ${tagContent}`
    );
    $(this).attr("id", tagSlug);
  });

  return $("body div").html();
};

export default anchorLinker;

同樣,這裡的想法是相同的 我們在上面學到的東西。唯一真正的區別是我們實現代碼的方式。因為我們return 在我們的 isClient 內部 塊,我們可以跳過 else 塊並直接從我們的函數體中返回服務器錨鏈接代碼。這是因為 if (isClient) 是真的,當 JavaScript 命中 return 聲明,它將停止評估超出該點的任何代碼。如果是 false ,它將跳過該塊並繼續我們的服務器端代碼。

專注於該代碼,我們首先使用 cheerio.load("<div></div>") 創建我們的內存中 DOM 創建一個空的 <div></div> 就像我們在上面所做的一樣。我們將其存儲在 $ 變量,因為 cheerio 從技術上講是“jQuery for Node.js”(加引號是因為 Cheerio 唯一的“jQuery”是它的 API 受 jQuery 影響——我們在這裡沒有使用任何 jQuery 代碼)。

與上麵類似,我們使用 $("body") 函數說“找到 body $ 內的標籤 我們剛剛生成的 DOM,然後在 that 中 找到任何 h1-h6 標記。”這應該看起來很熟悉。這與我們對 document.querySelectorAll() 所做的相同 早一點。

接下來,我們獲取標籤並遍歷它們。對於每個標籤,我們再次提取標籤的內部文本內容,將其轉換為帶有 getSlug() 的 slug 然後注入“錨定”<a></a> 標記回 hTag 最後,設置 id 屬性。這裡唯一可能令人困惑的是 this 的用法 而不是 hTag 就像我們在 .forEach() 中看到的那樣 在客戶端循環。

這裡,thishTags.each() 所在的當前上下文 循環正在運行(意味著它正在循環的當前元素)。雖然我們看不到,this 由 Cheerio 在幕後設定。

最後,緊接在我們的 .each() 之後 循環,我們返回 <div></div> 的 HTML 內容 我們用 cheerio.load() 創建的標籤 .

完畢!現在,我們已經準備好使用它,並看到一些錨鏈接被添加到我們的 HTML 中。

將錨鏈接器連接到 HTML

演示我們新的 anchorLinker() 的用法 函數,我們將在一些 lorem ipsum 段落之間連接一個帶有一些 Markdown 文本的簡單組件,包括一些 h1-h6 標籤:

/pages/index.js

import React from "react";
import anchorLinker from "../lib/anchorLinker";

import StyledIndex from "./index.css";

const paragraphs = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.`;

const testContent = `This is some test content to verify our anchorLinker() is working.

# This is an h1 anchor
${paragraphs}

## This is an h2 anchor
${paragraphs}

### This is an h3 anchor
${paragraphs}

#### This is and h4 anchor
${paragraphs}

##### This is an h5 anchor
${paragraphs}

###### This is an h6 anchor
${paragraphs}
`;

const Index = ({ prop1, prop2 }) => (
  <StyledIndex
    dangerouslySetInnerHTML={{
      __html: anchorLinker(testContent),
    }}
  />
);

Index.propTypes = {};

export default Index;

在這裡,我們要注意的部分是 const Index = () => {} 開頭的文件底部附近的 React 組件 .在這裡,我們返回一個樣式化組件 <StyledIndex /> 這有助於我們為我們的內容設置一些基本樣式(這是從 ./index.css 頂部導入的 )。我們不會在這裡詳細介紹樣式,但讓我們現在添加它們以避免混淆:

/pages/index.css.js

import styled from "styled-components";

export default styled.div`
  .anchor-link {
    color: #aaa;
    font-size: 18px;

    &:hover {
      color: var(--primary);
    }

    .fa-link {
      margin-right: 5px;
    }
  }

  h1,
  h2,
  h3,
  h4,
  h5,
  h6 {
    font-size: 20px;
    margin-bottom: 20px;
  }

  p {
    font-size: 16px;
    line-height: 26px;
    margin-bottom: 40px;
  }
`;

注意 :.css.js 這裡的文件名後綴是故意的。我們使用 styled-components 創建 CSS,它是通過 JavaScript 完成的,我們這樣命名它以暗示文件的內容是“用 JavaScript 編寫的 CSS”。

/pages/index.js

import React from "react";
import anchorLinker from "../lib/anchorLinker";

import StyledIndex from "./index.css";

const paragraphs = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.`;

const testContent = `This is some test content to verify our anchorLinker() is working.

# This is an h1 anchor
${paragraphs}

[...]
`;

const Index = ({ prop1, prop2 }) => (
  <StyledIndex
    dangerouslySetInnerHTML={{
      __html: anchorLinker(testContent),
    }}
  />
);

Index.propTypes = {};

export default Index;

回到我們的測試 <Index /> 組件,作為我們 <StyledIndex /> 上的一個道具 組件,我們設置 dangerouslySetInnerHTML 等於帶有 __html 的對象 包含調用我們導入的 anchorLinker() 的結果的屬性 函數並傳遞我們的 testContent 字符串(我們未編譯的 Markdown)。

請記住,在 anchorLinker() 內部 ,我們從我們的客戶端和服務器端版本的鏈接器返回一個 HTML 字符串。所以,當它最終返回時,在這裡,我們獲取該 HTML 字符串並將其設置為呈現的 <StyledIndex /> 的內容 React 中的元素。

換句話說?這將在瀏覽器中呈現我們的 HTML 的錨鏈接版本:

總結

在本教程中,我們學會瞭如何自動為HTML內容生成錨定標籤。我們學習瞭如何選擇和操作內存中的 DOM 元素,生成一個包含我們的錨鏈接的 HTML 字符串並在瀏覽器中呈現它。

我們還學會瞭如何利用Markdown通過commonmark為我們生成HTML 以及如何使用speakingurl生成slugified String .


Tutorial JavaScript 教程
  1. 調查:為什麼開發人員喜歡 Node.js

  2. 使用 Marionette 顯示模態視圖

  3. 解構你對 JavaScript 解構的困惑

  4. 將 javascript 動態插入到使用 document.write 的 HTML 中

  5. 星期一 17 單元:視差畫廊、視頻背景、綜合聯繫等

  6. Honeybadger 中 JavaScript 的麵包屑

  7. 如何在 React 中使用錯誤邊界和錯誤監控

  1. MERN Stack - 所有你需要知道的!

  2. 用 react js 改變不透明度和動畫

  3. 我是嗎?流媒體服務體驗

  4. 什麼是學習 TypeScript 的最佳場所?

  5. 使用 React 進行狀態重置和更新

  6. 通過 Rector 將 PHP 代碼從 8.0 轉換為 7.x

  7. 刷新 React 知識(第 1 部分)

  1. 刮擦 NodeGUI 的表面

  2. 幫助您練習 Web 開發的資源

  3. 最著名的 Javascript 庫更新

  4. 瀏覽器控制台的完整指南