调整模板渲染模式
This commit is contained in:
@@ -15,11 +15,14 @@ type CaptionElement struct {
|
||||
Y string `json:"y" d:"center" dc:"垂直位置:top/center/bottom或像素值"`
|
||||
FontSize int `json:"fontSize" d:"36" dc:"字号(type=text有效)"`
|
||||
FontColor string `json:"fontColor" d:"#FFFFFF" dc:"字体颜色"`
|
||||
FontStroke string `json:"fontStroke" dc:"字体描边,如3px #000000(空=无描边)"`
|
||||
BgColor string `json:"bgColor" dc:"背景色,如#FFFF00,空=透明"`
|
||||
BgOpacity float64 `json:"bgOpacity" d:"1.0" dc:"背景透明度0-1"`
|
||||
Width int `json:"width" dc:"元素宽度(type=image有效)"`
|
||||
Width int `json:"width" dc:"元素宽度(px)"`
|
||||
WidthStr string `json:"widthStr" dc:"宽度表达式(如calc(100% - 320px),非整数宽度用,type=text优先于width)"`
|
||||
Height int `json:"height" dc:"元素高度(type=image有效)"`
|
||||
Animation string `json:"animation" dc:"动画效果:fadeIn/slideUp/slideLeft/scaleIn"`
|
||||
Vertical bool `json:"vertical" dc:"竖排显示(type=text有效,如左右两侧竖排文案,渲染为writing-mode:vertical-rl)"`
|
||||
TrackIndex int `json:"trackIndex" d:"1" dc:"轨道层级(数字越大越靠前)"`
|
||||
}
|
||||
|
||||
|
||||
@@ -587,18 +587,34 @@ func (s *captionService) buildElemStyle(elem dto.CaptionElement, canvasWidth, ca
|
||||
|
||||
// 文字样式
|
||||
if elem.Type == "text" {
|
||||
// 自动计算 max-width 防止文字溢出画布
|
||||
// 底部居中字幕: 90% 宽度; 左/右贴边: 45% 宽度
|
||||
maxWidthPct := 90
|
||||
switch elem.X {
|
||||
case "left", "right":
|
||||
maxWidthPct = 45
|
||||
case "center", "":
|
||||
maxWidthPct = 90
|
||||
if elem.Vertical {
|
||||
// 竖排显示:writing-mode 竖排,宽度自动收缩为单列,不强制百分比宽度
|
||||
parts = append(parts, "writing-mode:vertical-rl;")
|
||||
} else {
|
||||
if elem.WidthStr != "" {
|
||||
// 模板指定了非整数宽度(如 calc(100% - 320px)):文字宽度随画布自适应,
|
||||
// 始终落在两图之间的空隙里,避免压到图片
|
||||
parts = append(parts, fmt.Sprintf("width:%s;", elem.WidthStr))
|
||||
parts = append(parts, fmt.Sprintf("max-width:%s;", elem.WidthStr))
|
||||
} else if elem.Width > 0 {
|
||||
// 模板指定了像素宽度:文字宽度精确受控,不再按画布百分比
|
||||
parts = append(parts, fmt.Sprintf("width:%dpx;", elem.Width))
|
||||
parts = append(parts, fmt.Sprintf("max-width:%dpx;", elem.Width))
|
||||
} else {
|
||||
// 自动计算 max-width 防止文字溢出画布
|
||||
// 底部居中字幕: 90% 宽度; 左/右贴边: 45% 宽度
|
||||
maxWidthPct := 90
|
||||
switch elem.X {
|
||||
case "left", "right":
|
||||
maxWidthPct = 45
|
||||
case "center", "":
|
||||
maxWidthPct = 90
|
||||
}
|
||||
maxWidth := int(float64(canvasWidth) * float64(maxWidthPct) / 100.0)
|
||||
parts = append(parts, fmt.Sprintf("width:%dpx;", maxWidth))
|
||||
parts = append(parts, fmt.Sprintf("max-width:%dpx;", maxWidth))
|
||||
}
|
||||
}
|
||||
maxWidth := int(float64(canvasWidth) * float64(maxWidthPct) / 100.0)
|
||||
parts = append(parts, fmt.Sprintf("width:%dpx;", maxWidth))
|
||||
parts = append(parts, fmt.Sprintf("max-width:%dpx;", maxWidth))
|
||||
|
||||
if elem.FontSize > 0 {
|
||||
parts = append(parts, fmt.Sprintf("font-size:%dpx;", elem.FontSize))
|
||||
@@ -606,6 +622,9 @@ func (s *captionService) buildElemStyle(elem dto.CaptionElement, canvasWidth, ca
|
||||
if elem.FontColor != "" {
|
||||
parts = append(parts, fmt.Sprintf("color:%s;", elem.FontColor))
|
||||
}
|
||||
if elem.FontStroke != "" {
|
||||
parts = append(parts, fmt.Sprintf("-webkit-text-stroke:%s;", elem.FontStroke))
|
||||
}
|
||||
}
|
||||
|
||||
return strings.Join(parts, "")
|
||||
|
||||
@@ -230,7 +230,22 @@ func (s *templateService) applyStyles(elem *dto.CaptionElement, styles map[strin
|
||||
elem.Y = cssPositionToY(top, styles)
|
||||
} else if bottom, ok := styles["bottom"]; ok {
|
||||
bottom = strings.TrimSpace(bottom)
|
||||
elem.Y = "calc(100% - " + bottom + ")"
|
||||
if bottom == "0" || bottom == "0px" {
|
||||
// bottom:0 → 语义化 "bottom",渲染端输出 bottom:0px(正确贴底)
|
||||
elem.Y = "bottom"
|
||||
} else {
|
||||
// bottom: Npx → 需元素高度才能换算 top,解析阶段拿不到高度;
|
||||
// 保留 calc(100% - Npx)(顶边在 100%-N 处)。带高度元素请用
|
||||
// top: calc(100% - 高度px - Npx) 写法
|
||||
elem.Y = "calc(100% - " + bottom + ")"
|
||||
}
|
||||
}
|
||||
|
||||
// 竖排显示(writing-mode: vertical-rl / vertical-lr)
|
||||
if wm, ok := styles["writing-mode"]; ok {
|
||||
if strings.Contains(strings.TrimSpace(wm), "vertical") {
|
||||
elem.Vertical = true
|
||||
}
|
||||
}
|
||||
|
||||
// 字号
|
||||
@@ -243,6 +258,11 @@ func (s *templateService) applyStyles(elem *dto.CaptionElement, styles map[strin
|
||||
elem.FontColor = strings.TrimSpace(color)
|
||||
}
|
||||
|
||||
// 字体描边(如 -webkit-text-stroke: 3px #000000,白字黑描边效果)
|
||||
if stroke, ok := styles["-webkit-text-stroke"]; ok {
|
||||
elem.FontStroke = strings.TrimSpace(stroke)
|
||||
}
|
||||
|
||||
// 背景色 + 透明度
|
||||
if bg, ok := styles["background"]; ok {
|
||||
bg = strings.TrimSpace(bg)
|
||||
@@ -266,7 +286,13 @@ func (s *templateService) applyStyles(elem *dto.CaptionElement, styles map[strin
|
||||
|
||||
// 图片宽高
|
||||
if w, ok := styles["width"]; ok && elem.Width <= 0 {
|
||||
elem.Width = parsePxToInt(w)
|
||||
w = strings.TrimSpace(w)
|
||||
if px := parsePxToInt(w); px > 0 {
|
||||
elem.Width = px
|
||||
} else if w != "" {
|
||||
// 非整数宽度(如 calc(100% - 320px)),原样透传,文字可精确控制换行范围
|
||||
elem.WidthStr = w
|
||||
}
|
||||
}
|
||||
if h, ok := styles["height"]; ok && elem.Height <= 0 {
|
||||
elem.Height = parsePxToInt(h)
|
||||
@@ -298,7 +324,7 @@ func parseInlineStyle(style string) map[string]string {
|
||||
// cssPositionToX 将 CSS left/transform 转为 X 定位值
|
||||
func cssPositionToX(left string, styles map[string]string) string {
|
||||
transform := styles["transform"]
|
||||
left = strings.TrimSuffix(left, "px")
|
||||
left = stripPx(left)
|
||||
|
||||
if left == "0" || left == "0px" {
|
||||
return "left"
|
||||
@@ -315,7 +341,7 @@ func cssPositionToX(left string, styles map[string]string) string {
|
||||
// cssPositionToY 将 CSS top/transform 转为 Y 定位值
|
||||
func cssPositionToY(top string, styles map[string]string) string {
|
||||
transform := styles["transform"]
|
||||
top = strings.TrimSuffix(top, "px")
|
||||
top = stripPx(top)
|
||||
|
||||
if top == "0" || top == "0px" {
|
||||
return "top"
|
||||
@@ -326,6 +352,19 @@ func cssPositionToY(top string, styles map[string]string) string {
|
||||
return top
|
||||
}
|
||||
|
||||
// stripPx 仅当整串为纯数值(N 或 Npx)时去掉 px 后缀,避免破坏 calc(...) 等表达式。
|
||||
// 例如 "200px" → "200",而 "calc(100% - 200px)" 原样保留。
|
||||
func stripPx(s string) string {
|
||||
trimmed := strings.TrimSpace(s)
|
||||
if strings.HasSuffix(trimmed, "px") {
|
||||
core := strings.TrimSuffix(trimmed, "px")
|
||||
if isNumeric(core) {
|
||||
return core
|
||||
}
|
||||
}
|
||||
return trimmed
|
||||
}
|
||||
|
||||
// parsePxToInt 从 "48px" 或 "48" 解析出 int
|
||||
func parsePxToInt(s string) int {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
+153
-88
@@ -13,6 +13,9 @@
|
||||
- 当前 1080 x 1920 是竖屏 9:16(手机全屏)
|
||||
- 想改分辨率就改这两个数字
|
||||
例如 720 x 1280 就是 720p 竖屏
|
||||
- 注意:这里只是"预览用"尺寸,实际渲染时画布高度会跟随
|
||||
你上传的视频分辨率变化,所以模板里贴底/贴右请用 calc(100% - Npx),
|
||||
不要写死像素值。
|
||||
- overflow: hidden → 超出画布的部分裁掉,不会出滚动条
|
||||
- background: #000 → 背景黑色,视频没铺满时露出的颜色
|
||||
*/
|
||||
@@ -89,7 +92,34 @@ body {
|
||||
★ data-track-index 属性 ★
|
||||
控制元素的前后层级,数字越大越靠前(盖住数字小的)
|
||||
track-index=0 是背景视频专用,其他元素从 1 开始
|
||||
======================================================================
|
||||
|
||||
★★ 渲染器 CSS 支持范围(写模板前必看,避免踩坑)★★
|
||||
模板只是一份"配置",服务端会把它解析成有限的元素模型,再重新生成
|
||||
HTML 渲染,因此不是所有 CSS 都会被保留。请遵守以下规则:
|
||||
|
||||
1. 定位支持:left / right / top / bottom 关键字、纯像素值、
|
||||
calc(...) 表达式;文字支持 writing-mode: vertical-rl 竖排。
|
||||
|
||||
2. ★ 不要用 bottom: Npx 给"有高度"的元素定位 ★
|
||||
它会被翻译成 top: calc(100% - Npx),把元素顶边放到画布
|
||||
100%-N 的位置,图片会整体掉出画面(表现为"图片丢失")。
|
||||
想让元素贴底,请写 top: calc(100% - 元素高度px);
|
||||
想离底 Npx,请写 top: calc(100% - 元素高度px - Npx)。
|
||||
|
||||
3. ★ right: Npx 的偏移量会被忽略 ★(等价 right:0,直接贴右边)
|
||||
想让元素距右边 Npx,请写 left: calc(100% - Npx - 元素宽度px)。
|
||||
|
||||
4. 竖排文字配 top: 50% + transform: translateY(-50%) 可垂直居中;
|
||||
水平居中仍用 left: 50% + transform: translateX(-50%)。
|
||||
|
||||
5. 文字描边支持 -webkit-text-stroke: Npx #000000
|
||||
(例如白字一圈黑边:-webkit-text-stroke: 1px #000000。
|
||||
★ 描边别太粗:中文字笔画细,28px 字号下 3px 描边会把白色
|
||||
填充盖成黑字;1px 稳妥,想明显可加到 2px。)
|
||||
|
||||
6. 文字也支持 width(模板指定 width 可覆盖渲染端的默认宽度,
|
||||
精确控制换行范围,如把文字限定在两张图片之间不压图)。
|
||||
======================================================================
|
||||
-->
|
||||
|
||||
<!--
|
||||
@@ -104,8 +134,8 @@ body {
|
||||
left: 50% + transform: translateX(-50%)
|
||||
水平居中,不要改
|
||||
|
||||
top: 70px
|
||||
距离顶部 70 像素
|
||||
top: 320px
|
||||
距离顶部 320 像素
|
||||
→ 改大数字,标题往下移;改小数字,标题往上移
|
||||
|
||||
font-size: 48px
|
||||
@@ -123,7 +153,7 @@ body {
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 70px;
|
||||
top: 320px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 48px;
|
||||
color: #D82828;
|
||||
@@ -131,7 +161,7 @@ body {
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_image_1" 产品图片
|
||||
data-key="product_image_1" 产品图片(底部左边)
|
||||
|
||||
注意:这是 <img> 图片标签,不是文字
|
||||
|
||||
@@ -140,14 +170,20 @@ body {
|
||||
width: 200px / height: 200px
|
||||
图片的宽和高
|
||||
→ 改大图片变大,改小图片变小
|
||||
→ 改高度时,下面 top: calc(100% - 200px) 里的 200 也要跟着改,
|
||||
保持等于图片高度,图片才会正好贴底。
|
||||
|
||||
left: 0px
|
||||
距离左边 0 像素(贴左边缘)
|
||||
left: 200px
|
||||
距离左边 200 像素(图片不在贴左边缘,而是往中间收)
|
||||
→ 改大数字,图片往右移
|
||||
|
||||
top: 200px
|
||||
距离顶部 200 像素
|
||||
→ 改大数字,图片往下移;改小数字,图片往上移
|
||||
top: calc(100% - 200px)
|
||||
距离顶部 = 画布高度 - 200px,即图片贴住底部
|
||||
(100% 是父容器全部高度;200 = 图片高度)
|
||||
→ 注意:这里不能用 bottom: 0px!会被解析器翻译成
|
||||
top: calc(100% - 0px),把图片顶边放到画布最底,图片就
|
||||
整块掉出画面了(这就是之前"图片丢失"的原因)。
|
||||
→ 想离底部 Npx,写成 calc(100% - 200px - Npx)
|
||||
|
||||
提示:
|
||||
如果你有多张产品图片,可以复制这个元素,
|
||||
@@ -159,127 +195,156 @@ body {
|
||||
style="
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
left: 0px;
|
||||
top: 200px;
|
||||
left: 1px;
|
||||
top: calc(100% - 200px);
|
||||
"
|
||||
src="占位图片.jpg" />
|
||||
<img data-key="product_image_2" data-start="0" data-duration="0" data-track-index="2"
|
||||
class="clip"
|
||||
style="
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
right: 1px;
|
||||
top: calc(100% - 200px);
|
||||
"
|
||||
src="占位图片.jpg" />
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_desc" 产品卖点描述(第一行)
|
||||
data-key="product_desc" 产品卖点描述(底部两个图片之间)
|
||||
|
||||
最终效果:
|
||||
- 白字,水平居中,落在 product_image_1 与 product_image_2 之间的空隙里
|
||||
- width: calc(100% - 320px) 让文字宽度随画布自适应,正好落在两图空隙内,
|
||||
任何分辨率下都不会压到图片
|
||||
- 因为 image_2 图片比较小(比 image_1 矮,只占上面一段),
|
||||
文字的前几行会被左右两张图片夹住;后面的几行高度超过 image_2 后,
|
||||
右侧空出来,文字就能延伸到 image_2 图片的下方显示
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: 830px
|
||||
距离顶部 830 像素
|
||||
→ 改大数字,文字往下移;改小数字,文字往上移
|
||||
top: calc(100% - 180px)
|
||||
文字顶部到画布底部的距离(100% 是父容器全部高度)
|
||||
→ 改大 180 这个数字,整段文字往上移(更远离底部图片)
|
||||
→ 改小 180 这个数字,文字往下移
|
||||
→ 想要"前几行被两张图夹住、后几行落到 image_2 下方",
|
||||
就把这个数字控制在 100~200 之间(两张图都从距底 200px 处开始)
|
||||
|
||||
width: calc(100% - 320px)
|
||||
文字宽度 = 画布宽 - 320px
|
||||
(320 = image_1 宽 200 + image_2 宽 100 + 左右边距各 10px)
|
||||
→ 渲染端支持模板指定非整数宽度(calc/百分比),不再强制画布 90%
|
||||
→ 想让文字更宽,把 320 改小;想留更多边距,把 320 改大
|
||||
|
||||
left: 210px
|
||||
距画布左边 210px(image_1 在左边缘宽 200px,210 正好给它右边留 10px)
|
||||
→ 文字整体往左/往右移,改这个数字
|
||||
|
||||
font-size: 34px
|
||||
字号
|
||||
→ 改大字变大,改小字变小
|
||||
|
||||
color: #333333
|
||||
文字颜色
|
||||
→ #333333 = 深灰色
|
||||
→ 改成 #000000 就是纯黑色
|
||||
→ 改成 #FFFFFF 就是白色
|
||||
color: #FFFFFF
|
||||
文字颜色(白色)
|
||||
|
||||
提示:文字内容里可以用 \n 换行
|
||||
-->
|
||||
<div data-key="product_desc" data-start="0" data-duration="0" data-track-index="3"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 830px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 34px;
|
||||
color: #333333;
|
||||
left: 210px;
|
||||
top: calc(100% - 180px);
|
||||
width: calc(100% - 320px);
|
||||
font-size: 17px;
|
||||
color: #FFFFFF;
|
||||
">产品卖点描述</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_info" 产品信息(第二行描述,更靠下的位置)
|
||||
data-key="product_info" 产品信息(左边竖排显示)
|
||||
|
||||
用法和上面的 product_desc 一样,只是位置不同
|
||||
最终效果:贴左边缘,文字从上往下竖排显示
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: 950px
|
||||
距离顶部 950 像素
|
||||
→ 比 product_desc 更靠下
|
||||
writing-mode: vertical-rl
|
||||
竖排显示(文字从上往下写)
|
||||
这是竖排效果的关键,不要删(服务端已支持)
|
||||
|
||||
left: 380px
|
||||
距离左边 380 像素(往中间收)
|
||||
→ 改大数字,文字往右移
|
||||
|
||||
top: 50% + transform: translateY(-50%)
|
||||
垂直居中,让竖排文字位于左边中间位置
|
||||
|
||||
font-size: 28px
|
||||
字号比第一行略小
|
||||
|
||||
color: #444444
|
||||
文字颜色,比第一行稍浅
|
||||
-->
|
||||
<div data-key="product_info" data-start="0" data-duration="0" data-track-index="4"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 950px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 28px;
|
||||
color: #444444;
|
||||
">第二行描述文字</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="disclaimer" 底部免责声明
|
||||
|
||||
最终效果:贴在底部,白字黑底半透明
|
||||
|
||||
★ 注意 ★
|
||||
这个元素内部有一个 <span> 标签,背景色写在 span 上,
|
||||
程序会自动识别 span 上的背景色并应用到最终视频。
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: calc(100% - 45px)
|
||||
距离底部 45 像素(100% 是父容器的全部高度)
|
||||
→ 改大 45 这个数字,文字往上移(离底部更远)
|
||||
→ 改小 45 这个数字,文字往下移(贴底部更近)
|
||||
|
||||
font-size: 20px
|
||||
字号
|
||||
字号(和 disclaimer 一致)
|
||||
|
||||
color: #FFFFFF
|
||||
文字颜色(白色)
|
||||
|
||||
★ 下面 <span> 标签里的样式 ★
|
||||
-webkit-text-stroke: 1px #000000
|
||||
文字描边(黑色描边一圈)
|
||||
→ 描边别太粗:中文字笔画细,3px 会把白色盖成黑字,1px 稳妥
|
||||
→ 想更明显改成 2px;改成 0 或删掉这行 = 不要描边
|
||||
-->
|
||||
<div data-key="product_info" data-start="0" data-duration="0" data-track-index="4"
|
||||
class="clip text-element"
|
||||
style="
|
||||
writing-mode: vertical-rl;
|
||||
left: 680px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28px;
|
||||
color: #FFFFFF;
|
||||
-webkit-text-stroke: 1px #000000;
|
||||
">产品信息文字</div>
|
||||
|
||||
background: rgba(0, 0, 0, 0.70)
|
||||
黑底半透明背景
|
||||
→ 括号里前三个数字 0,0,0 是黑色(改成 255,255,255 就是白色底)
|
||||
→ 最后一个数字 0.70 是不透明度
|
||||
1.0 = 完全不透明(纯黑底)
|
||||
0.5 = 半透明
|
||||
0.0 = 完全透明(看不见背景)
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="disclaimer" 免责声明(竖排显示)
|
||||
|
||||
padding: 12px 24px
|
||||
背景的内边距
|
||||
→ 上下 12px,左右 24px
|
||||
→ 改大数字,背景范围变大(文字周围的空隙变大)
|
||||
→ 改小数字,背景范围变小
|
||||
最终效果:竖排显示,白字黑描边
|
||||
|
||||
border-radius: 8px
|
||||
背景圆角
|
||||
→ 改大数字(如 20px),圆角更明显
|
||||
→ 改成 0,就是直角
|
||||
★ 可调参数 ★
|
||||
|
||||
writing-mode: vertical-rl
|
||||
竖排显示(文字从上往下写)
|
||||
这是竖排效果的关键,不要删(服务端已支持)
|
||||
|
||||
left: calc(100% - 730px)
|
||||
距画布左边 730 像素(竖排文字宽约 30px,当前位于画面偏左)
|
||||
→ 改大 730 这个数字,整段往左移;改小 → 往右移
|
||||
→ 注意:这里不能写 right: Npx!偏移量会被解析器忽略,
|
||||
等价于 right:0(直接贴右边)。
|
||||
|
||||
top: 50% + transform: translateY(-50%)
|
||||
垂直居中,让竖排文字位于中间位置
|
||||
|
||||
font-size: 28px
|
||||
字号(和 product_info 一致)
|
||||
|
||||
color: #FFFFFF
|
||||
文字颜色(白色)
|
||||
|
||||
-webkit-text-stroke: 1px #000000
|
||||
文字描边(黑色描边一圈)
|
||||
→ 描边别太粗:中文字笔画细,3px 会把白色盖成黑字,1px 稳妥
|
||||
→ 想更明显改成 2px;改成 0 或删掉这行 = 不要描边
|
||||
-->
|
||||
<div data-key="disclaimer" data-start="0" data-duration="0" data-track-index="5"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: calc(100% - 45px);
|
||||
transform: translateX(-50%);
|
||||
font-size: 20px;
|
||||
writing-mode: vertical-rl;
|
||||
left: calc(100% - 730px);
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
font-size: 28px;
|
||||
color: #FFFFFF;
|
||||
-webkit-text-stroke: 1px #000000;
|
||||
">
|
||||
<span style="background:rgba(0,0,0,0.70); padding:12px 24px; border-radius:8px;">
|
||||
免责声明文字
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<style>
|
||||
/* ========== 全局重置:去掉浏览器默认边距 ========== */
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
|
||||
/* ========== 画布尺寸 ========== */
|
||||
/*
|
||||
width / height:最终视频的输出分辨率
|
||||
- 当前 1080 x 1920 是竖屏 9:16(手机全屏)
|
||||
- 想改分辨率就改这两个数字
|
||||
例如 720 x 1280 就是 720p 竖屏
|
||||
- overflow: hidden → 超出画布的部分裁掉,不会出滚动条
|
||||
- background: #000 → 背景黑色,视频没铺满时露出的颜色
|
||||
*/
|
||||
body {
|
||||
width: 1080px;
|
||||
height: 1920px;
|
||||
overflow: hidden;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
/* .clip:所有元素都要有这个 class,才能用 left/top 精确控制位置 */
|
||||
.clip { position: absolute; }
|
||||
|
||||
/* .text-element:所有文字元素都要有这个 class */
|
||||
.text-element {
|
||||
font-family: Arial, Helvetica, sans-serif; /* 字体 */
|
||||
text-align: center; /* 文字水平居中 */
|
||||
display: flex; /* 弹性盒子布局(不用管) */
|
||||
align-items: center; /* 垂直居中 */
|
||||
justify-content: center; /* 水平居中 */
|
||||
white-space: pre-wrap; /* 保留换行符,文字超出自动换行 */
|
||||
word-break: break-word; /* 长单词撑破时强制换行 */
|
||||
}
|
||||
|
||||
/* ========== 入场动画 ========== */
|
||||
/* 给元素加上 class="anim-xxx" 就能用对应的动画效果 */
|
||||
/*
|
||||
动画时长怎么改?
|
||||
数字越小动画越快,越大越慢
|
||||
例如 0.5s → 改成 1s 就慢一倍,改成 0.2s 就快一倍
|
||||
*/
|
||||
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
|
||||
@keyframes slideUp { from { opacity: 0; transform: translateY(40px); } to { opacity: 1; transform: translateY(0); } }
|
||||
@keyframes slideLeft { from { opacity: 0; transform: translateX(40px); } to { opacity: 1; transform: translateX(0); } }
|
||||
@keyframes scaleIn { from { opacity: 0; transform: scale(0.8); } to { opacity: 1; transform: scale(1); } }
|
||||
@keyframes pulse { 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } }
|
||||
|
||||
.anim-fadeIn { animation: fadeIn 0.5s ease-out; }
|
||||
.anim-slideUp { animation: slideUp 0.6s ease-out; }
|
||||
.anim-slideLeft { animation: slideLeft 0.6s ease-out; }
|
||||
.anim-scaleIn { animation: scaleIn 0.5s ease-out; }
|
||||
.anim-pulse { animation: pulse 1.5s ease-in-out infinite; }
|
||||
</style>
|
||||
<script src="https://cdn.jsdelivr.net/npm/gsap@3/dist/gsap.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
id="stage" → 舞台容器,所有元素都放在这里面
|
||||
|
||||
重要属性说明:
|
||||
data-composition-id="main" → 不要改,程序内部标识
|
||||
data-width / data-height → 必须和上面 body 的宽高一致
|
||||
======================================================================
|
||||
-->
|
||||
<div id="stage" data-composition-id="main" data-start="0" data-width="1080" data-height="1920">
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
★ data-key 属性 ★
|
||||
每个元素必须有一个 data-key 属性,程序会根据 data-key 的名字,
|
||||
把你传的 data 数据替换到元素内容上。
|
||||
|
||||
例如:
|
||||
模板里 <div data-key="title">标题文字</div>
|
||||
你传参时 data: { "title": "你好世界" }
|
||||
程序就会把"标题文字"替换成"你好世界"
|
||||
|
||||
★ data-start / data-duration 属性 ★
|
||||
data-start="0" → 第 0 秒开始显示
|
||||
data-duration="0" → 持续显示到视频结束(0=一直显示)
|
||||
|
||||
★ data-track-index 属性 ★
|
||||
控制元素的前后层级,数字越大越靠前(盖住数字小的)
|
||||
track-index=0 是背景视频专用,其他元素从 1 开始
|
||||
======================================================================
|
||||
-->
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="title" 标题
|
||||
|
||||
最终效果:居中靠上,红色大字
|
||||
======================================================================
|
||||
|
||||
★ 你可以改下面这些参数来调整效果 ★
|
||||
|
||||
left: 50% + transform: translateX(-50%)
|
||||
水平居中,不要改
|
||||
|
||||
top: 70px
|
||||
距离顶部 70 像素
|
||||
→ 改大数字,标题往下移;改小数字,标题往上移
|
||||
|
||||
font-size: 48px
|
||||
字号(字体大小)
|
||||
→ 改大数字,字变大;改小数字,字变小
|
||||
|
||||
color: #D82828
|
||||
文字颜色,十六进制颜色码
|
||||
→ 常见颜色:
|
||||
#FFFFFF = 白色 #000000 = 黑色
|
||||
#D82828 = 红色 #333333 = 深灰
|
||||
→ 也可以直接写英文 red / white / black
|
||||
-->
|
||||
<div data-key="title" data-start="0" data-duration="0" data-track-index="1"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 70px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 48px;
|
||||
color: #D82828;
|
||||
">标题文字</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_image_1" 产品图片
|
||||
|
||||
注意:这是 <img> 图片标签,不是文字
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
width: 200px / height: 200px
|
||||
图片的宽和高
|
||||
→ 改大图片变大,改小图片变小
|
||||
|
||||
left: 0px
|
||||
距离左边 0 像素(贴左边缘)
|
||||
→ 改大数字,图片往右移
|
||||
|
||||
top: 200px
|
||||
距离顶部 200 像素
|
||||
→ 改大数字,图片往下移;改小数字,图片往上移
|
||||
|
||||
提示:
|
||||
如果你有多张产品图片,可以复制这个元素,
|
||||
把 data-key 改成 product_image_2、product_image_3 ...
|
||||
然后在传参时给对应的 key 传不同的图片 URL
|
||||
-->
|
||||
<img data-key="product_image_1" data-start="0" data-duration="0" data-track-index="2"
|
||||
class="clip"
|
||||
style="
|
||||
width: 200px;
|
||||
height: 200px;
|
||||
left: 0px;
|
||||
top: 200px;
|
||||
"
|
||||
src="占位图片.jpg" />
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_desc" 产品卖点描述(第一行)
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: 830px
|
||||
距离顶部 830 像素
|
||||
→ 改大数字,文字往下移;改小数字,文字往上移
|
||||
|
||||
font-size: 34px
|
||||
字号
|
||||
→ 改大字变大,改小字变小
|
||||
|
||||
color: #333333
|
||||
文字颜色
|
||||
→ #333333 = 深灰色
|
||||
→ 改成 #000000 就是纯黑色
|
||||
→ 改成 #FFFFFF 就是白色
|
||||
|
||||
提示:文字内容里可以用 \n 换行
|
||||
-->
|
||||
<div data-key="product_desc" data-start="0" data-duration="0" data-track-index="3"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 830px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 34px;
|
||||
color: #333333;
|
||||
">产品卖点描述</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="product_info" 产品信息(第二行描述,更靠下的位置)
|
||||
|
||||
用法和上面的 product_desc 一样,只是位置不同
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: 950px
|
||||
距离顶部 950 像素
|
||||
→ 比 product_desc 更靠下
|
||||
|
||||
font-size: 28px
|
||||
字号比第一行略小
|
||||
|
||||
color: #444444
|
||||
文字颜色,比第一行稍浅
|
||||
-->
|
||||
<div data-key="product_info" data-start="0" data-duration="0" data-track-index="4"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: 950px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 28px;
|
||||
color: #444444;
|
||||
">第二行描述文字</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
data-key="disclaimer" 底部免责声明
|
||||
|
||||
最终效果:贴在底部,白字黑底半透明
|
||||
|
||||
★ 注意 ★
|
||||
这个元素内部有一个 <span> 标签,背景色写在 span 上,
|
||||
程序会自动识别 span 上的背景色并应用到最终视频。
|
||||
|
||||
★ 可调参数 ★
|
||||
|
||||
top: calc(100% - 45px)
|
||||
距离底部 45 像素(100% 是父容器的全部高度)
|
||||
→ 改大 45 这个数字,文字往上移(离底部更远)
|
||||
→ 改小 45 这个数字,文字往下移(贴底部更近)
|
||||
|
||||
font-size: 20px
|
||||
字号
|
||||
|
||||
color: #FFFFFF
|
||||
文字颜色(白色)
|
||||
|
||||
★ 下面 <span> 标签里的样式 ★
|
||||
|
||||
background: rgba(0, 0, 0, 0.70)
|
||||
黑底半透明背景
|
||||
→ 括号里前三个数字 0,0,0 是黑色(改成 255,255,255 就是白色底)
|
||||
→ 最后一个数字 0.70 是不透明度
|
||||
1.0 = 完全不透明(纯黑底)
|
||||
0.5 = 半透明
|
||||
0.0 = 完全透明(看不见背景)
|
||||
|
||||
padding: 12px 24px
|
||||
背景的内边距
|
||||
→ 上下 12px,左右 24px
|
||||
→ 改大数字,背景范围变大(文字周围的空隙变大)
|
||||
→ 改小数字,背景范围变小
|
||||
|
||||
border-radius: 8px
|
||||
背景圆角
|
||||
→ 改大数字(如 20px),圆角更明显
|
||||
→ 改成 0,就是直角
|
||||
-->
|
||||
<div data-key="disclaimer" data-start="0" data-duration="0" data-track-index="5"
|
||||
class="clip text-element"
|
||||
style="
|
||||
left: 50%;
|
||||
top: calc(100% - 45px);
|
||||
transform: translateX(-50%);
|
||||
font-size: 20px;
|
||||
color: #FFFFFF;
|
||||
">
|
||||
<span style="background:rgba(0,0,0,0.70); padding:12px 24px; border-radius:8px;">
|
||||
免责声明文字
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!--
|
||||
======================================================================
|
||||
以下脚本不要改,这是程序内部使用的 GSAP 时间轴初始化
|
||||
======================================================================
|
||||
-->
|
||||
<script>
|
||||
(function() {
|
||||
var tl = gsap.timeline({ paused: true });
|
||||
tl.to('#stage', { duration: 0, opacity: 1 }, 0);
|
||||
window.__timelines = window.__timelines || {};
|
||||
window.__timelines['main'] = tl;
|
||||
})();
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user