diff --git a/apps/web/src/pages/generate/components/title/TitlePresetsGrid.tsx b/apps/web/src/pages/generate/components/title/TitlePresetsGrid.tsx index bed206054..6c411ea69 100644 --- a/apps/web/src/pages/generate/components/title/TitlePresetsGrid.tsx +++ b/apps/web/src/pages/generate/components/title/TitlePresetsGrid.tsx @@ -1,5 +1,7 @@ /** * 标题预设样式网格 + * 双图层渲染:底层=描边轮廓(text-shadow模拟),上层=填充色 + * 避免 -webkit-text-stroke 在 Chromium 中吞掉填充色的问题 */ import React from "react" import { getFontFamily } from "../../constants" @@ -7,7 +9,10 @@ import { getFontFamily } from "../../constants" interface TitlePresetItem { key: string label: string - previewStyle: React.CSSProperties + previewStyle: React.CSSProperties & { + _strokeColor?: string + _strokeWidth?: number + } } interface TitlePresetsGridProps { @@ -17,6 +22,32 @@ interface TitlePresetsGridProps { fontFamily?: string } +/** + * 用 text-shadow 模拟描边轮廓(8方向 + 4对角 = 12层阴影) + */ +function buildStrokeShadow(color: string, width: number): string { + const w = width + const parts: string[] = [] + // 4 cardinal directions + parts.push(`${w}px 0 ${color}`) + parts.push(`${-w}px 0 ${color}`) + parts.push(`0 ${w}px ${color}`) + parts.push(`0 ${-w}px ${color}`) + // 4 diagonal directions + const d = Math.round(w * 0.71 * 10) / 10 // 0.71 ≈ sqrt(2)/2 + parts.push(`${d}px ${d}px ${color}`) + parts.push(`${-d}px ${d}px ${color}`) + parts.push(`${d}px ${-d}px ${color}`) + parts.push(`${-d}px ${-d}px ${color}`) + // 4 extra mid-points for smoother stroke + const h = Math.round(w * 0.5 * 10) / 10 + parts.push(`${w}px ${h}px ${color}`) + parts.push(`${w}px ${-h}px ${color}`) + parts.push(`${-w}px ${h}px ${color}`) + parts.push(`${-w}px ${-h}px ${color}`) + return parts.join(", ") +} + const TitlePresetsGrid: React.FC = ({ presets, activePreset, @@ -27,6 +58,30 @@ const TitlePresetsGrid: React.FC = ({
{presets.map((p) => { const isActive = activePreset === p.key + const { _strokeColor, _strokeWidth, ...fillStyle } = p.previewStyle + const ff = getFontFamily(fontFamily || "思源黑体") + + // 底层:描边轮廓(用 text-shadow 模拟粗描边) + const strokeStyle: React.CSSProperties = { + color: _strokeColor || "transparent", + textShadow: + _strokeColor && _strokeWidth + ? buildStrokeShadow(_strokeColor, _strokeWidth) + : undefined, + fontWeight: fillStyle.fontWeight, + fontSize: fillStyle.fontSize, + lineHeight: 1, + } + + // 上层:仅填充色 + 可选 textShadow(发光/投影效果) + const topStyle: React.CSSProperties = { + color: fillStyle.color, + textShadow: fillStyle.textShadow, + fontWeight: fillStyle.fontWeight, + fontSize: fillStyle.fontSize, + lineHeight: 1, + } + return ( ) diff --git a/apps/web/src/pages/generate/constants.ts b/apps/web/src/pages/generate/constants.ts index 9b4ed569c..0b591c10a 100644 --- a/apps/web/src/pages/generate/constants.ts +++ b/apps/web/src/pages/generate/constants.ts @@ -78,9 +78,10 @@ export const TITLE_PRESETS = [ label: "经典白字", style: { size: 28, color: "#ffffff", bold: true, italic: false, stroke: true, shadow: false }, previewStyle: { - fontWeight: 700, color: "#ffffff", - WebkitTextStroke: "1px #000000", + _strokeColor: "#000000", + _strokeWidth: 2, + fontWeight: 700, fontSize: "32px", }, }, @@ -89,10 +90,12 @@ export const TITLE_PRESETS = [ label: "黑金质感", style: { size: 32, color: "#d4a843", bold: true, italic: false, stroke: false, shadow: true }, previewStyle: { - fontWeight: 700, color: "#d4a843", - textShadow: "1px 1px 3px rgba(0,0,0,0.8)", + _strokeColor: "#1a1000", + _strokeWidth: 1.5, + fontWeight: 700, fontSize: "32px", + textShadow: "1px 1px 3px rgba(0,0,0,0.8)", }, }, { @@ -100,10 +103,11 @@ export const TITLE_PRESETS = [ label: "清新简约", style: { size: 24, color: "#333333", bold: false, italic: false, stroke: false, shadow: false }, previewStyle: { + color: "#e8e8e8", + _strokeColor: "#cccccc", + _strokeWidth: 1, fontWeight: 400, - color: "#333333", fontSize: "32px", - WebkitTextStroke: "0.5px #888888", }, }, { @@ -111,11 +115,12 @@ export const TITLE_PRESETS = [ label: "综艺花字", style: { size: 36, color: "#ff4081", bold: true, italic: false, stroke: true, shadow: true }, previewStyle: { - fontWeight: 900, color: "#ff4081", - WebkitTextStroke: "1.5px #ffffff", - textShadow: "2px 2px 4px rgba(0,0,0,0.5)", + _strokeColor: "#ffffff", + _strokeWidth: 2, + fontWeight: 900, fontSize: "32px", + textShadow: "2px 2px 4px rgba(0,0,0,0.5)", }, }, { @@ -123,10 +128,11 @@ export const TITLE_PRESETS = [ label: "商务极简", style: { size: 24, color: "#1a1a1a", bold: false, italic: false, stroke: false, shadow: false }, previewStyle: { + color: "#e0e0e0", + _strokeColor: "#bbbbbb", + _strokeWidth: 1, fontWeight: 400, - color: "#1a1a1a", fontSize: "32px", - WebkitTextStroke: "0.5px #999999", }, }, { @@ -134,10 +140,12 @@ export const TITLE_PRESETS = [ label: "复古胶片", style: { size: 28, color: "#e8d5b7", bold: false, italic: false, stroke: false, shadow: true }, previewStyle: { - fontWeight: 400, color: "#e8d5b7", - textShadow: "2px 2px 6px rgba(0,0,0,0.7)", + _strokeColor: "#3a2a1a", + _strokeWidth: 1.5, + fontWeight: 400, fontSize: "32px", + textShadow: "2px 2px 6px rgba(0,0,0,0.7)", }, }, { @@ -145,10 +153,12 @@ export const TITLE_PRESETS = [ label: "霓虹发光", style: { size: 32, color: "#00e5ff", bold: true, italic: false, stroke: false, shadow: true }, previewStyle: { - fontWeight: 700, color: "#00e5ff", - textShadow: "0 0 4px #00e5ff, 0 0 8px #00e5ff, 0 0 16px rgba(0,229,255,0.5)", + _strokeColor: "#00e5ff", + _strokeWidth: 1, + fontWeight: 700, fontSize: "32px", + textShadow: "0 0 4px #00e5ff, 0 0 8px #00e5ff, 0 0 16px rgba(0,229,255,0.5)", }, }, { @@ -156,11 +166,12 @@ export const TITLE_PRESETS = [ label: "手写字", style: { size: 28, color: "#333333", bold: false, italic: false, stroke: false, shadow: true }, previewStyle: { + color: "#e0e0e0", + _strokeColor: "#cccccc", + _strokeWidth: 1, fontWeight: 400, - color: "#333333", - WebkitTextStroke: "0.5px #888888", - textShadow: "1px 1px 2px rgba(0,0,0,0.3)", fontSize: "32px", + textShadow: "1px 1px 2px rgba(0,0,0,0.3)", }, }, ] diff --git a/apps/web/src/pages/generate/generate.css b/apps/web/src/pages/generate/generate.css index e6bfbdc69..6584094b7 100644 --- a/apps/web/src/pages/generate/generate.css +++ b/apps/web/src/pages/generate/generate.css @@ -1734,7 +1734,7 @@ .xx-title-presets-grid { display: grid; grid-template-columns: repeat(8, 1fr); - gap: 6px; + gap: 1.5px; } .xx-title-preset-card { @@ -1763,7 +1763,6 @@ .xx-title-preset-preview-text { font-size: 32px; - font-weight: 700; line-height: 1; user-select: none; }