[FE] Learn from react-wrap-balancer

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

使用方式

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
留言分享你的想法!
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
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
每年4月、5月都是最多稅要繳的月份,當然大部份的人都是有機會繳到「綜合所得稅」,只是相當相當多人還不知道,原來繳給政府的稅!可以透過一些有活動的銀行信用卡或電子支付來繳,從繳費中賺一點點小確幸!就是賺個1%~2%大家也是很開心的,因為你們把沒回饋變成有回饋,就是用卡的最高境界 所得稅線上申報
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
全球科技產業的焦點,AKA 全村的希望 NVIDIA,於五月底正式發布了他們在今年 2025 第一季的財報 (輝達內部財務年度為 2026 Q1,實際日曆期間為今年二到四月),交出了打敗了市場預期的成績單。然而,在銷售持續高速成長的同時,川普政府加大對於中國的晶片管制......
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
重點摘要: 6 月繼續維持基準利率不變,強調維持高利率主因為關稅 點陣圖表現略為鷹派,收斂 2026、2027 年降息預期 SEP 連續 2 季下修 GDP、上修通膨預測值 --- 1.繼續維持利率不變,強調需要維持高利率是因為關稅: 聯準會 (Fed) 召開 6 月利率會議
Thumbnail
CSS 是控制網頁外觀的語言,應用於網頁設計、UI/UX 設計、電子商務和移動應用開發。主要使用者包括前端開發者、UI/UX 設計師和網頁設計師。CSS 的特性有樣式控制、層疊優先級、響應式設計及分離內容與樣式。
Thumbnail
CSS 是控制網頁外觀的語言,應用於網頁設計、UI/UX 設計、電子商務和移動應用開發。主要使用者包括前端開發者、UI/UX 設計師和網頁設計師。CSS 的特性有樣式控制、層疊優先級、響應式設計及分離內容與樣式。
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組件狀態的做法分享
Thumbnail
在程式任何地方都能修改各種react組件狀態的做法分享
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
JavaScript 套件,頁碼 Pagination.js 搭配 axios API 請求範例
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
前言 現在的前端需求已經越來越高,要考慮HTML及CSS的切版美觀程度,以及React以及Flutter所提出的元件(Componet、widget)觀念,也就是將元件模組化,使元件可以更動態的被程式運行,而不用靜態的客製化每一個介面。開發一個好的元件可以提升整體的開發速度,讓任何使用元件的開發者
Thumbnail
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
這是一個介紹React Text Wrap Balancer套件的文章,主要內容包括套件的使用方式,常見的實作方式和一些注意事項。文章內容較長,內容大概是在介紹套件的使用方法、使用技巧和注意事項。
Thumbnail
您對前端有興趣,對CSS不陌生。CSS新特性前應瞭解基本內容。CSS屬性制定經歷過程,瀏覽器私有前綴及處理,開發者對新特性漸進增強,優雅降級處理,Web標準定義、作用等。
Thumbnail
您對前端有興趣,對CSS不陌生。CSS新特性前應瞭解基本內容。CSS屬性制定經歷過程,瀏覽器私有前綴及處理,開發者對新特性漸進增強,優雅降級處理,Web標準定義、作用等。
Thumbnail
React Props - React 白話文運動。這一篇則會針對 React 語法進一步做講解, 前幾篇也學會了如何建立一個 React 元件,並且學會了元件之間有父子元件的關係。這篇會介紹 React Props 的概念,包含了:React 元件宣告、React Props。
Thumbnail
React Props - React 白話文運動。這一篇則會針對 React 語法進一步做講解, 前幾篇也學會了如何建立一個 React 元件,並且學會了元件之間有父子元件的關係。這篇會介紹 React Props 的概念,包含了:React 元件宣告、React Props。
Thumbnail
此系列教學最後一篇補上使用Hexo後關於Next主題相關的小套件與設定。 提醒小套件的功能視需求而安裝,畢竟安裝太多小套件也是會有執行後效能的問題浮現。
Thumbnail
此系列教學最後一篇補上使用Hexo後關於Next主題相關的小套件與設定。 提醒小套件的功能視需求而安裝,畢竟安裝太多小套件也是會有執行後效能的問題浮現。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News