vocus logo

方格子 vocus

[FE] Learn from react-wrap-balancer

Todd-avatar-img
發佈於FE
更新 發佈閱讀 6 分鐘
此套件主要是使字體擁有更好的閱讀體驗,它會將每一行的文字都有差不多的長度,達成更好的閱讀體驗。
https://react-wrap-balancer.vercel.app
vocus|新世代的創作平台

使用方式

import Balancer from "react-wrap-balancer";

<h1>
<Balancer>My Title</Balancer>
</h1>;

codesandbox

Text Wrap: balance

此套件如果瀏覽器可以使用到原生的 CSS text-wrap: balance  的話,就會使用 native 的方式,不支援才會透過計算來達成。

這是一種 CSS 原生的文字排版方式,用途一樣是為了讓文字更容易閱讀,身為開發者是無法預測最終呈現的文字是什麼(i18n, 文字的字體等),但是瀏覽器可以,所以透過瀏覽器的實作去計算成最容易閱讀的方式。這實作是需要耗費成本的,所以並不推薦將所有的文字都加上,並且瀏覽器也有限制最多只會影響到 6 行的文字。

h1,
h2,
h3,
h4,
h5,
h6,
blockquote {
text-wrap: balance;
}

另外此種方式與 white-space  是衝突的, balance  會造成換行,所以不應該一起使用。

套件中使用 JS 的 CSS.suports  來檢查是否支援使用此方式。

const isTextWrapBalanceSupported = `(self.CSS&&CSS.supports("text-wrap","balance")?1:2)`

useIsomorphicLayoutEffect

常見的 Server Side 和 Client Side 的 effect 實作方式,如果在 Server Side 的話,因為沒有 DOM 元素,所以如果使用 useLayoutEffect  會沒有實際的作用,因此改使用 useEffect 。

export const IS_SERVER = typeof window === "undefined";

export const useIsomorphicLayoutEffect = IS_SERVER
? React.useEffect
: React.useLayoutEffect;

useIdPollyfill

react 中可以使用 useId  來獲取唯一的 ID 值,不過在舊版並沒有此 hook,所以在套件中另外實作了此 hook 的 pollyfill。

let ID = 0

const genId = () => ++IDlet serverHandoffComplete = falsefunction useIdPolyfill() { const [id, setId] = React.useState(serverHandoffComplete ? genId : undefined) useIsomorphicLayoutEffect(() => { if (id === undefined) { setId(genId()) } serverHandoffComplete = true }, []) if (id === undefined) { return id } return `rwb-${id.toString(32)}`}

其中 genId 使用到了 js 的 closure 的概念,它獲取外部的 ID 變數並進行修改。

在使用時檢查開發者目前的 react 是否有支援 useId 的 hook,若沒有的話則使用實作的 useIdPollyfill 。

export function useId() {

const implementation = React.useMemo((): (() => string | number) => {
if ('useId' in React) return React.useId
return useIdPolyfill
}, [])
return implementation()
}

Polymorphic Component

這是一種由外部決定 Component 要使用什麼 HTML Tag 的方式,並透過 TypeScript 讓開發有更好的體驗,可以知道有哪些屬性可以傳遞。

codesandbox

eact 提供了 ComponentPropsWithoutRef 傳入 generic type 獲取該 Component 有哪些的 props 可以傳遞,聽夠過 Omit 移除已經定義的屬性。

interface BalancerOwnProps<

 ElementType extends React.ElementType = React.ElementType

> extends React.HTMLAttributes<HTMLElement> {

...

}



type BalancerProps<ElementType extends React.ElementType> =

 BalancerOwnProps<ElementType> &

   Omit<React.ComponentPropsWithoutRef<ElementType>, keyof BalancerOwnProps>

props nonce

因為此套件會動態的載入 <script> ,而在 CSP(Content Security Policy) 不允許有在 HTML 內的 <script> Tag,除非有設定 nonce 屬性。

const createScriptElement = (
injected: boolean,
nonce?: string,
suffix: string = ''
) => {
if (suffix) {
suffix = `self.${SYMBOL_NATIVE_KEY}!=1&&${suffix}`
}
return (
<script
suppressHydrationWarning
dangerouslySetInnerHTML={{
// Calculate the balance initially for SSR
__html:
(injected
? ''
: `self.${SYMBOL_NATIVE_KEY}=self.${SYMBOL_NATIVE_KEY}||${isTextWrapBalanceSupported};self.${SYMBOL_KEY}=${RELAYOUT_STR};`) +
suffix,
}}
nonce={nonce}
/>
)
}

suppressHydrationWarning 是指產生的內容和在 Server Side 產生的不相同的話不需要提示。

在此 Script 會執行

// self.${SYMBOL_NATIVE_KEY}=self.${SYMBOL_NATIVE_KEY}||${isTextWrapBalanceSupported}
self.__wrap_n = self.__wrap_n || isTextWrapBalanceSupported;
// self.${SYMBOL_KEY}=${RELAYOUT_STR};
self.__wrap_b = RELAYOUT_STR
// suffix: self.${SYMBOL_KEY}("${id}",${ratio})
relayout(id, ratio)

relayout

這是此套件計算方式的 function。

const relayout: RelayoutFn = (id, ratio, wrapper) => {

}

傳入的 id , ratio , wrapper ,其中 wrapper 指的是會包住我們內容的 Element,預設是 span 。

  wrapper =
wrapper || document.querySelector<WrapperElement>(`[data-br="${id}"]`)
const container = wrapper.parentElement

const update = (width: number) => (wrapper.style.maxWidth = width + 'px')

// Reset wrapper width
wrapper.style.maxWidth = ''

這一段若是沒有給 wrapper 的話會透過 querySelector 獲取,並重設 wrapper  element 的 maxWith 。

// Get the initial container size
const width = container.clientWidth
const height = container.clientHeight
// Synchronously do binary search and calculate the layout
let lower: number = width / 2 - 0.25
let upper: number = width + 0.5
let middle: number

if (width) {
// Ensure we don't search widths lower than when the text overflows
update(lower)
lower = Math.max(wrapper.scrollWidth, lower)

while (lower + 1 < upper) {
middle = Math.round((lower + upper) / 2)
update(middle)
if (container.clientHeight === height) {
upper = middle
} else {
lower = middle
}
}

// Update the wrapper width
update(upper * ratio + width * (1 - ratio))
}

這一段比較長,主要是使用 binary search 找到最適合的寬度,若是 lowerupper 差不止 1px 就會執行,如果父元素原本的高度(height)和後來的高度(container.clientHeight) 相同會將 upper 設為 middle ,因為在上方已經將 wrapper 的 maxWith 進行更改,所以可能會造成 wrapper 內的‘文字換行造成父元素的高度也改變,若沒有造成換行的話則將 lower 設為 middle 。

之後在依照我們給的 ratio 計算新的 maxWidth ,如果 ratio 為 1 就會使用 upper 的值,0 則使用原本的值。

 if (!wrapper['__wrap_o']) {
if (typeof ResizeObserver !== 'undefined') {
;(wrapper['__wrap_o'] = new ResizeObserver(() => {
self.__wrap_b(0, +wrapper.dataset.brr, wrapper)
})).observe(container)
} else {
// Silently ignore ResizeObserver for production builds
if (process.env.NODE_ENV === 'development') {
console.warn(
'The browser you are using does not support the ResizeObserver API. ' +
'Please consider add polyfill for this API to avoid potential layout shifts or upgrade your browser. ' +
'Read more: https://github.com/shuding/react-wrap-balancer#browser-support-information'
)
}
}
}

之後若沒有 ResizeObserver 的話,建一個新的去監聽 wrapper 的 size 變化重新去執行。

留言
avatar-img
Hello Todd
2會員
5內容數
FE Developer
Hello Todd的其他內容
2024/12/23
本篇文章介紹了網頁元素的 Box 佈局,細述 Content Box、Padding Box、Border Box 和 Margin Box 的結構,並探討了 Intrinsic 與 Extrinsic Size 的特性。
Thumbnail
2024/12/23
本篇文章介紹了網頁元素的 Box 佈局,細述 Content Box、Padding Box、Border Box 和 Margin Box 的結構,並探討了 Intrinsic 與 Extrinsic Size 的特性。
Thumbnail
2024/06/09
透過GraphQL提供的分頁方式,優化後端讀取資料的效能,避免過度讀取舊資料及準確指定特定項目。同時,利用Local-only field達成資料的整理或再次經過計算,提升管理和重複使用的效能。
Thumbnail
2024/06/09
透過GraphQL提供的分頁方式,優化後端讀取資料的效能,避免過度讀取舊資料及準確指定特定項目。同時,利用Local-only field達成資料的整理或再次經過計算,提升管理和重複使用的效能。
Thumbnail
2024/02/29
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
Thumbnail
2024/02/29
Service Worker 是用於客戶端的攔截器,可以使用 Cache Storage 和 IndexDB,並有自己的生命週期。Web Worker 用於處理可能會使用到大量運算且不希望影響到使用者體驗的任務。Shared Worker 可以在相同 Domain 的不同頁面上共享訊息。
Thumbnail
看更多
你可能也想看
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
此系列教學最後一篇補上使用Hexo後關於Next主題相關的小套件與設定。 提醒小套件的功能視需求而安裝,畢竟安裝太多小套件也是會有執行後效能的問題浮現。
Thumbnail
此系列教學最後一篇補上使用Hexo後關於Next主題相關的小套件與設定。 提醒小套件的功能視需求而安裝,畢竟安裝太多小套件也是會有執行後效能的問題浮現。
Thumbnail
您對前端有興趣,對CSS不陌生。CSS新特性前應瞭解基本內容。CSS屬性制定經歷過程,瀏覽器私有前綴及處理,開發者對新特性漸進增強,優雅降級處理,Web標準定義、作用等。
Thumbnail
您對前端有興趣,對CSS不陌生。CSS新特性前應瞭解基本內容。CSS屬性制定經歷過程,瀏覽器私有前綴及處理,開發者對新特性漸進增強,優雅降級處理,Web標準定義、作用等。
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
因為最近想嘗試編碼風格,於是就選了一套比較"不嚴格"的輔助工具來摸索。 編輯器 VS CODE 框架 VUE3 打包工具 VITE 編碼風格 Standard 環境 version { "nodejs":"v18.18.0", "npm":"9.8.1" }
Thumbnail
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
Thumbnail
React Props - React 白話文運動。這一篇則會針對 React 語法進一步做講解, 前幾篇也學會了如何建立一個 React 元件,並且學會了元件之間有父子元件的關係。這篇會介紹 React Props 的概念,包含了:React 元件宣告、React Props。
Thumbnail
React Props - React 白話文運動。這一篇則會針對 React 語法進一步做講解, 前幾篇也學會了如何建立一個 React 元件,並且學會了元件之間有父子元件的關係。這篇會介紹 React Props 的概念,包含了:React 元件宣告、React Props。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
《轉轉生》(Re:INCARNATION)為奈及利亞編舞家庫德斯.奧尼奎庫與 Q 舞團創作的當代舞蹈作品,結合拉各斯街頭節奏、Afrobeat/Afrobeats、以及約魯巴宇宙觀的非線性時間,建構出關於輪迴的「誕生—死亡—重生」儀式結構。本文將從約魯巴哲學概念出發,解析其去殖民的身體政治。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
本文分析導演巴里・柯斯基(Barrie Kosky)如何運用極簡的舞臺配置,將布萊希特(Bertolt Brecht)的「疏離效果」轉化為視覺奇觀與黑色幽默,探討《三便士歌劇》在當代劇場中的新詮釋,並藉由舞臺、燈光、服裝、音樂等多方面,分析該作如何在保留批判核心的同時,觸及觀眾的觀看位置與人性幽微。
Thumbnail
CSS 是控制網頁外觀的語言,應用於網頁設計、UI/UX 設計、電子商務和移動應用開發。主要使用者包括前端開發者、UI/UX 設計師和網頁設計師。CSS 的特性有樣式控制、層疊優先級、響應式設計及分離內容與樣式。
Thumbnail
CSS 是控制網頁外觀的語言,應用於網頁設計、UI/UX 設計、電子商務和移動應用開發。主要使用者包括前端開發者、UI/UX 設計師和網頁設計師。CSS 的特性有樣式控制、層疊優先級、響應式設計及分離內容與樣式。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
這是一場修復文化與重建精神的儀式,觀眾不需要完全看懂《遊林驚夢:巧遇Hagay》,但你能感受心與土地團聚的渴望,也不急著在此處釐清或定義什麼,但你的在場感受,就是一條線索,關於如何找著自己的路徑、自己的聲音。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
Thumbnail
5 月將於臺北表演藝術中心映演的「2026 北藝嚴選」《海妲・蓋柏樂》,由臺灣劇團「晃晃跨幅町」製作,本文將以從舞台符號、聲音與表演調度切入,討論海妲・蓋柏樂在父權社會結構下的困境,並結合榮格心理學與馮.法蘭茲對「阿尼姆斯」與「永恆少年」原型的分析,理解女人何以走向精神性的操控、毀滅與死亡。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News