網頁直式排版初體驗

受到日本的直書排版推廣委員會啟發,決定也來嘗試一波。

用 CSS 做直書排版

在需要直書的區塊中(例如範例中的 div),加入 writing-mode 樣式即可。

div {
    writing-mode: vertical-rl;
}

sample div with only writing-mode.

不能再更簡單。多加入一些文字看看。

直書排版版面橫向溢出

當畫面較小的時候,網頁自動排版會溢出。當然我們可以模擬橫書的解法,加上高度限制,例如:

div {
    writing-mode: vertical-rl;
    height: 100%;
}

然後你就會得到一篇超~級寬的竹簡。若能確保每篇頁面的字數都不會太多,其實也沒關係。例如《新明解国語辞典》線上日文辭典就是這樣實作。

新明解国語辞典のスクショ

用 MacBook 的觸控板可以很容易左右滑動。而對於使用滑鼠的人,頂多加上一些簡單 Javascript 語法,把滾動方向改成左右滑動即可。但文章一長,這將會是災難般的閱讀體驗。所以我們勢必得找到方法讓長篇文章可以垂直分段。

直書分段

為直書垂直分段,單純使用 height 樣式調整的話,會導致所有文字往左邊溢出。這邊將使用分欄的小技巧,讓文字在 div 區塊寬度和高度同時受限制之下,依然能夠自然往下分段。又在文字排版完之前,我們無從得知最終區塊高度,所以要移除 height 設定。改成如下:

div {
    writing-mode: vertical-rl;
    width: 100%;  /* 寬度限制 */
    column-width: 7rem;  /* 每段高度 7rem,約 7 個中文字 */
    column-rule: solid 1px #ccc;  /* 加入段落之前的分隔線 */
    column-gap: 1em;  /* 設定段落之間的距離 */
}

再讓我們看一次效果:

加入分欄做分段的效果圖

搞定?還沒。還有些問題需要調整,讓我們繼續看下去。

加入 Javascript 修正

單純靠 CSS,我們能做的調整有限。從這裡開始加入一些 JS 輔助。

英數混雜的排版顯示調整

首先,英文數字全部倒橫了。長的就當作沒辦法,一兩個英文字母、或者兩位數以內數字等,我們希望可以把它轉 90 度,讓我們不用常常歪頭。

雖然這件事 CSS 可以做到,但是需要在字的兩旁加入 HTML 標籤。才能夠搭配相應 CSS 來調整顯示方向,例如:

.tcy {
    text-combine-upright: all;
}
<div>哆啦<span class="tcy">A</span>夢</div>

將輸出以下:

A加入HTML標籤並以CSS調整方向

手動加入太麻煩,讓我們寫一點 JS 來自動做這件事。

function textCombineUpright( elem ) {
    const regs = [
        /(\s)(¥?\b\d{1,2}\b)(\s)/g,  // short number, ie: 4, 12, ...
        /(\s)\b([A-Z]{1,2})\b(\s)/g, // short capitalized chars, ie: A, IE, ...
    ];
    regs.forEach(reg => {
        elem.innerHTML = elem.innerHTML.replaceAll(reg, '<span class="tcy" data-before="$1" data-after="$3">$2</span>');
    });
}

window.onload = function() {
    // 請自行修改目標對象 Selector
	  const postContent = document.querySelector('div');
    const targetElements = [
        'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6',
        'blockquote',
    ];
    targetElements.forEach(elem => {
        postContent.querySelectorAll(elem).forEach( dom => {
            textCombineUpright(dom);
        });
    });
};

這樣只要文章中出現「空白包夾的短英文、或數字」,就會自動幫我們加入帶有 .tcy Class 的 span 標籤,以套用轉向樣式。

加入JS來自動為2字元內的英數轉向

看起來舒服多了。但還有一個大問題。

高度無法自動調配問題

這時候的文章高度其實是超過 div 本身的。讓我們在 div 的 CSS 中加入 outline: 2px solid red; 印個紅色外框看看。

div height not align article height screenshot

div 後面還有東西,第二段以後的文字會全部和後面元素重疊在一起,無法閱讀。

原因是因為,目前的 CSS 中沒有指定欄位數量,而預設是 1 欄,在直書排版中就是一個段落。但是每篇文章的字數不一,因此所需段落數量也不一樣。也就沒辦法在 CSS 中寫死一個固定值。網路找了半天找到了一個 JS 解法:直接將區塊高度指定為與內文等高。底下是一個簡化過的版本。有需要的人請再自行修改條件。

function getAbsolutePosition(element) {
    /*
        getBoundingClientRect gives us relative offsets to the screen,
        it changes when scrolling,
        so convert to absolute position before we want to calculate the height.
    */

    const rect = element.getBoundingClientRect();

    return {
        top: rect.top + window.scrollY,    // Add vertical scroll
        left: rect.left + window.scrollX,  // Add horizontal scroll
        bottom: rect.bottom + window.scrollY,
        right: rect.right + window.scrollX,
        height: rect.height,
    };
}

window.onload = function() {
    // 請自行修改目標對象 Selector
	  const postContent = document.querySelector( 'div' );

    function onResize(container) {
        let childs = Array.prototype.slice.call( container.children );
        let maxBottom = 0;
        // 歷遍子元素,以找出最底下的 Offset
        childs.forEach( c => {
            if (c.innerHTML.trim() == '') {
                return;
            }
            const bound = getAbsolutePosition(c);
            let currButtom = bound.top + bound.height;
            if (currButtom > maxBottom) {
                maxBottom = currButtom;
            }
        });
        // 將最底 Offset 扣掉 父元素的 Top 以設定為容器高度
        const topOffset = getAbsolutePosition(container.children[0]).top;
        container.style.height = (maxBottom - topOffset) + 'px';
  	}

    onResize(postContent);
};

粗暴,但簡單。終於搞定!

fix vertical context height with js

排版調整

標點符號處理

當一切看似安好,直到從 Safari 換到 Firefox 再看一次。

:等標點符號在不同瀏覽器上顯示的方向不一樣。

「:」等標點符號的方向竟然不一樣!不知道是字體的問題,還是 Firefox 不懂中文直式排版。也許換個字體就可以搞定。想說為了相容性,稍微調整一下當初做文字轉向的 Javascript。把冒號分號也加入轉向樣式。

function textCombineUpright( elem ) {
    const regs = [
        /(\s)(¥?\b\d{1,2}\b)(\s)/g,  // short number, ie: 4, 12, ...
        /(\s)\b([A-Z]{1,2})\b(\s)/g, // short capitalized chars, ie: A, IE, ...
        /()([:;])()/g,  // add this line
    ];
    regs.forEach(reg => {
        elem.innerHTML = elem.innerHTML.replaceAll(reg, '<span class="tcy" data-before="$1" data-after="$3">$2</span>');
    });
}

搞定。預覽圖就不放了。

字元間距調整

如果你覺得標點符號的距離有點遠,可以加入 font-feature-settings 樣式做微調。例如:

font-feature-settings: "halt";

仔細看逗號頓號等標點符號,可以發現字元間距縮短,排版較為緊密。青菜蘿蔔各有喜好,按照個人需求增加即可。也不是每個字體都有支援,請自行參閱下方參考資料連結做嘗試。

add font-feature-settings screenshot

後記

在英文橫行天下的時代,網頁設計也大多為了混排中英夾雜的文章,而採用橫式設計。CSS 本身的設計模式,當然也不例外,預設是由左上往右下排列。網頁滾動,也是上下滑動最為自然。因此對於任一橫版排列的網頁——就算是阿拉伯文等由右往左邊讀的文字——CSS 都得心應手。

雖然中日文等文字,直書橫書都可以,但直排才是漢字文化圈傳統,是其特色。身為一名中文使用者,我不入地獄誰入地獄,也就決定來試試直書在網頁上能夠呈現的效果。為普通的部落格,增添一點文藝氣息。

希望有更多的中文圈使用者,也一起來挑戰一下直書排版網頁 🙂

參考資料