需從 HTML 片段中截取前 N 個字元 (含中文),且**不能破壞標籤結構** (如 `` 未閉合)。直接字串截斷會導致標籤殘缺,前端渲染錯誤。
$tidy = tidy_parse_string($htmlFragment, [
'output-xhtml' => true,
'show-body-only' => true,
'wrap' => 0
], 'utf8'); // 注意:'utf8' 非 'utf-8'
$body = tidy_get_body($tidy); // 取得 節點物件
function truncateHtml($node, &$len, &$output) {
if ($node->type === TIDY_NODETEXT) {
$text = $node->value;
$width = mb_strwidth($text, 'utf-8'); // 中文算 2 寬度
if ($width <= $len) {
$output .= $text;
$len -= $width;
} else {
$output .= mb_strimwidth($text, 0, $len, '…', 'utf-8');
$len = 0;
return false; // 停止遍歷
}
} elseif ($node->type === TIDY_NODestart || $node->type === TIDY_NODEEND) {
// 標籤節點:直接輸出原始標籤
$output .= $node->value;
}
// 遞迴子節點
if (isset($node->child)) {
foreach ($node->child as $child) {
if (!$len) break;
if (!truncateHtml($child, $len, $output)) break;
}
}
return true;
}
// 使用
$maxWidth = 100; // 目標寬度 (中文 50 字)
$output = '';
truncateHtml($body, $maxWidth, $output);
echo $output; // 完整標籤 + 截斷後文字
mb_strwidth():依字元寬度計算長度 (中文=2, 英文=1),完美符合排版需求。mb_strimwidth():同樣依寬度截斷並可加省略號。| 方案 | 優點 | 缺點 |
|---|---|---|
| Tidy + DOM (本文) | 標籤 100% 完整、支援巢狀、寬度精確 | 需安裝 tidy 擴展、效能稍低 |
| 正則 + 狀態機 | 無擴展相依 | 極難處理巢狀/屬性含 `>`/CDATA/註解等邊界情況 |
前端 CSS text-overflow:ellipsis | 零後端成本、效能最好 | Firefox 早期不支援、多行截斷需 `-webkit-line-clamp`、SEO 看不到完整內容 |
function html_truncate($html, $maxWidth, $ellipsis = '…')。