feat(字幕样式): 增加字幕容器和文本元素的背景样式设置功能

添加字幕容器和各类文本元素(提示文字、消息文字、热词标题、热词问题)的背景颜色、透明度、圆角和内边距设置功能
新增范围滑块样式和实时值显示
实现从现有字幕容器加载样式配置的功能
扩展样式应用逻辑以处理新增的背景相关属性
This commit is contained in:
2026-05-06 15:31:06 +08:00
parent 240861f933
commit 633ce3014f
3 changed files with 288 additions and 6 deletions

View File

@@ -77,7 +77,33 @@
<h2>📝 字幕样式设置</h2>
<button class="panel-close" data-panel="captionPanel">×</button>
</div>
<div class="panel-content" style="max-height: 500px; overflow-y: auto;">
<div class="panel-content" style="max-height: 600px; overflow-y: auto;">
<!-- 字幕容器样式 -->
<div class="caption-style-section">
<h3>字幕容器 (container)</h3>
<div class="form-group">
<label for="containerBgColor">背景颜色</label>
<input type="color" id="containerBgColor" value="#1a1a2e">
</div>
<div class="form-group">
<label for="containerBgOpacity">背景透明度</label>
<input type="range" id="containerBgOpacity" value="85" min="0" max="100">
<span class="range-value">85%</span>
</div>
<div class="form-group">
<label for="containerBorderColor">边框颜色</label>
<input type="color" id="containerBorderColor" value="#333333">
</div>
<div class="form-group">
<label for="containerBorderRadius">圆角 (px)</label>
<input type="number" id="containerBorderRadius" value="8" min="0" max="20">
</div>
<div class="form-group">
<label for="containerPadding">内边距 (px)</label>
<input type="number" id="containerPadding" value="10" min="0" max="30">
</div>
</div>
<!-- 提示文字样式 -->
<div class="caption-style-section">
<h3>提示文字 (tip)</h3>
@@ -100,6 +126,23 @@
<label for="tipLineHeight">行高</label>
<input type="number" id="tipLineHeight" value="1.5" min="1" max="3" step="0.1">
</div>
<div class="form-group">
<label for="tipBgColor">背景颜色</label>
<input type="color" id="tipBgColor" value="#2a2a3e">
</div>
<div class="form-group">
<label for="tipBgOpacity">背景透明度</label>
<input type="range" id="tipBgOpacity" value="60" min="0" max="100">
<span class="range-value">60%</span>
</div>
<div class="form-group">
<label for="tipBorderRadius">圆角 (px)</label>
<input type="number" id="tipBorderRadius" value="4" min="0" max="15">
</div>
<div class="form-group">
<label for="tipPadding">内边距 (px)</label>
<input type="number" id="tipPadding" value="6" min="0" max="20">
</div>
</div>
<!-- 消息文字样式 -->
@@ -124,6 +167,23 @@
<label for="messageLineHeight">行高</label>
<input type="number" id="messageLineHeight" value="1.5" min="1" max="3" step="0.1">
</div>
<div class="form-group">
<label for="messageBgColor">背景颜色</label>
<input type="color" id="messageBgColor" value="#2a2a3e">
</div>
<div class="form-group">
<label for="messageBgOpacity">背景透明度</label>
<input type="range" id="messageBgOpacity" value="60" min="0" max="100">
<span class="range-value">60%</span>
</div>
<div class="form-group">
<label for="messageBorderRadius">圆角 (px)</label>
<input type="number" id="messageBorderRadius" value="4" min="0" max="15">
</div>
<div class="form-group">
<label for="messagePadding">内边距 (px)</label>
<input type="number" id="messagePadding" value="8" min="0" max="20">
</div>
</div>
<!-- 热词标题样式 -->
@@ -148,6 +208,23 @@
<label for="hotTitleLineHeight">行高</label>
<input type="number" id="hotTitleLineHeight" value="1.5" min="1" max="3" step="0.1">
</div>
<div class="form-group">
<label for="hotTitleBgColor">背景颜色</label>
<input type="color" id="hotTitleBgColor" value="#3a3a4e">
</div>
<div class="form-group">
<label for="hotTitleBgOpacity">背景透明度</label>
<input type="range" id="hotTitleBgOpacity" value="60" min="0" max="100">
<span class="range-value">60%</span>
</div>
<div class="form-group">
<label for="hotTitleBorderRadius">圆角 (px)</label>
<input type="number" id="hotTitleBorderRadius" value="4" min="0" max="15">
</div>
<div class="form-group">
<label for="hotTitlePadding">内边距 (px)</label>
<input type="number" id="hotTitlePadding" value="6" min="0" max="20">
</div>
</div>
<!-- 热词问题样式 -->
@@ -172,6 +249,23 @@
<label for="hotIssuesLineHeight">行高</label>
<input type="number" id="hotIssuesLineHeight" value="1.5" min="1" max="3" step="0.1">
</div>
<div class="form-group">
<label for="hotIssuesBgColor">背景颜色</label>
<input type="color" id="hotIssuesBgColor" value="#2a2a3e">
</div>
<div class="form-group">
<label for="hotIssuesBgOpacity">背景透明度</label>
<input type="range" id="hotIssuesBgOpacity" value="60" min="0" max="100">
<span class="range-value">60%</span>
</div>
<div class="form-group">
<label for="hotIssuesBorderRadius">圆角 (px)</label>
<input type="number" id="hotIssuesBorderRadius" value="4" min="0" max="15">
</div>
<div class="form-group">
<label for="hotIssuesPadding">内边距 (px)</label>
<input type="number" id="hotIssuesPadding" value="6" min="0" max="20">
</div>
</div>
<button id="applyCaptionStyleBtn" class="btn btn-primary" style="width: 100%; margin-top: 16px;">应用样式</button>

View File

@@ -60,25 +60,47 @@ const elements = {
toggleLogBtn: document.getElementById('toggleLogBtn'),
applyCaptionStyleBtn: document.getElementById('applyCaptionStyleBtn'),
containerBgColor: document.getElementById('containerBgColor'),
containerBgOpacity: document.getElementById('containerBgOpacity'),
containerBorderColor: document.getElementById('containerBorderColor'),
containerBorderRadius: document.getElementById('containerBorderRadius'),
containerPadding: document.getElementById('containerPadding'),
tipFontSize: document.getElementById('tipFontSize'),
tipFontWeight: document.getElementById('tipFontWeight'),
tipColor: document.getElementById('tipColor'),
tipLineHeight: document.getElementById('tipLineHeight'),
tipBgColor: document.getElementById('tipBgColor'),
tipBgOpacity: document.getElementById('tipBgOpacity'),
tipBorderRadius: document.getElementById('tipBorderRadius'),
tipPadding: document.getElementById('tipPadding'),
messageFontSize: document.getElementById('messageFontSize'),
messageFontWeight: document.getElementById('messageFontWeight'),
messageColor: document.getElementById('messageColor'),
messageLineHeight: document.getElementById('messageLineHeight'),
messageBgColor: document.getElementById('messageBgColor'),
messageBgOpacity: document.getElementById('messageBgOpacity'),
messageBorderRadius: document.getElementById('messageBorderRadius'),
messagePadding: document.getElementById('messagePadding'),
hotTitleFontSize: document.getElementById('hotTitleFontSize'),
hotTitleFontWeight: document.getElementById('hotTitleFontWeight'),
hotTitleColor: document.getElementById('hotTitleColor'),
hotTitleLineHeight: document.getElementById('hotTitleLineHeight'),
hotTitleBgColor: document.getElementById('hotTitleBgColor'),
hotTitleBgOpacity: document.getElementById('hotTitleBgOpacity'),
hotTitleBorderRadius: document.getElementById('hotTitleBorderRadius'),
hotTitlePadding: document.getElementById('hotTitlePadding'),
hotIssuesFontSize: document.getElementById('hotIssuesFontSize'),
hotIssuesFontWeight: document.getElementById('hotIssuesFontWeight'),
hotIssuesColor: document.getElementById('hotIssuesColor'),
hotIssuesLineHeight: document.getElementById('hotIssuesLineHeight'),
hotIssuesBgColor: document.getElementById('hotIssuesBgColor'),
hotIssuesBgOpacity: document.getElementById('hotIssuesBgOpacity'),
hotIssuesBorderRadius: document.getElementById('hotIssuesBorderRadius'),
hotIssuesPadding: document.getElementById('hotIssuesPadding'),
logLevel: document.getElementById('logLevel'),
@@ -369,42 +391,165 @@ function applyCaptionStyle() {
return;
}
const tipEl = captionContainer.querySelector('.layout-hub-tip');
const messageEl = captionContainer.querySelector('.layout-hub-message');
const hotTitleEl = captionContainer.querySelector('.layout-hub-hot-title');
const hotIssuesEl = captionContainer.querySelector('.layout-hub-hot-issues');
captionContainer.style.backgroundColor = hexToRgba(
elements.containerBgColor.value,
elements.containerBgOpacity.value / 100
);
captionContainer.style.borderColor = elements.containerBorderColor.value;
captionContainer.style.borderRadius = `${elements.containerBorderRadius.value}px`;
captionContainer.style.padding = `${elements.containerPadding.value}px`;
const tipEl = captionContainer.querySelector('.layout-hub-tip');
if (tipEl) {
tipEl.style.fontSize = `${elements.tipFontSize.value}px`;
tipEl.style.fontWeight = elements.tipFontWeight.value;
tipEl.style.color = elements.tipColor.value;
tipEl.style.lineHeight = elements.tipLineHeight.value;
tipEl.style.backgroundColor = hexToRgba(
elements.tipBgColor.value,
elements.tipBgOpacity.value / 100
);
tipEl.style.borderRadius = `${elements.tipBorderRadius.value}px`;
tipEl.style.padding = `${elements.tipPadding.value}px`;
}
const messageEl = captionContainer.querySelector('.layout-hub-message');
if (messageEl) {
messageEl.style.fontSize = `${elements.messageFontSize.value}px`;
messageEl.style.fontWeight = elements.messageFontWeight.value;
messageEl.style.color = elements.messageColor.value;
messageEl.style.lineHeight = elements.messageLineHeight.value;
messageEl.style.backgroundColor = hexToRgba(
elements.messageBgColor.value,
elements.messageBgOpacity.value / 100
);
messageEl.style.borderRadius = `${elements.messageBorderRadius.value}px`;
messageEl.style.padding = `${elements.messagePadding.value}px`;
}
const hotTitleEl = captionContainer.querySelector('.layout-hub-hot-title');
if (hotTitleEl) {
hotTitleEl.style.fontSize = `${elements.hotTitleFontSize.value}px`;
hotTitleEl.style.fontWeight = elements.hotTitleFontWeight.value;
hotTitleEl.style.color = elements.hotTitleColor.value;
hotTitleEl.style.lineHeight = elements.hotTitleLineHeight.value;
hotTitleEl.style.backgroundColor = hexToRgba(
elements.hotTitleBgColor.value,
elements.hotTitleBgOpacity.value / 100
);
hotTitleEl.style.borderRadius = `${elements.hotTitleBorderRadius.value}px`;
hotTitleEl.style.padding = `${elements.hotTitlePadding.value}px`;
}
const hotIssuesEl = captionContainer.querySelector('.layout-hub-hot-issues');
if (hotIssuesEl) {
hotIssuesEl.style.fontSize = `${elements.hotIssuesFontSize.value}px`;
hotIssuesEl.style.fontWeight = elements.hotIssuesFontWeight.value;
hotIssuesEl.style.color = elements.hotIssuesColor.value;
hotIssuesEl.style.lineHeight = elements.hotIssuesLineHeight.value;
hotIssuesEl.style.backgroundColor = hexToRgba(
elements.hotIssuesBgColor.value,
elements.hotIssuesBgOpacity.value / 100
);
hotIssuesEl.style.borderRadius = `${elements.hotIssuesBorderRadius.value}px`;
hotIssuesEl.style.padding = `${elements.hotIssuesPadding.value}px`;
}
logger.info('字幕样式已应用');
}
function hexToRgba(hex, alpha) {
const r = parseInt(hex.slice(1, 3), 16);
const g = parseInt(hex.slice(3, 5), 16);
const b = parseInt(hex.slice(5, 7), 16);
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
}
function loadCaptionStyleFromContainer() {
const captionContainer = document.querySelector('.caption-container-hub');
if (!captionContainer) return;
const containerStyles = window.getComputedStyle(captionContainer);
const containerBg = containerStyles.backgroundColor;
const containerBorder = containerStyles.borderColor;
const containerRadius = containerStyles.borderRadius;
const containerPad = containerStyles.padding;
if (containerBg && containerBg !== 'transparent') {
const rgbaMatch = containerBg.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
if (rgbaMatch) {
elements.containerBgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
elements.containerBgOpacity.value = rgbaMatch[4] ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
}
}
if (containerBorder && containerBorder !== 'transparent' && containerBorder !== 'rgba(0, 0, 0, 0)') {
const rgbaMatch = containerBorder.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
if (rgbaMatch) {
elements.containerBorderColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
}
}
elements.containerBorderRadius.value = parseInt(containerRadius) || 8;
elements.containerPadding.value = parseInt(containerPad) || 10;
updateRangeValue(elements.containerBgOpacity);
const sections = [
{ selector: '.layout-hub-tip', elements: { fontSize: elements.tipFontSize, fontWeight: elements.tipFontWeight, color: elements.tipColor, lineHeight: elements.tipLineHeight, bgColor: elements.tipBgColor, bgOpacity: elements.tipBgOpacity, borderRadius: elements.tipBorderRadius, padding: elements.tipPadding } },
{ selector: '.layout-hub-message', elements: { fontSize: elements.messageFontSize, fontWeight: elements.messageFontWeight, color: elements.messageColor, lineHeight: elements.messageLineHeight, bgColor: elements.messageBgColor, bgOpacity: elements.messageBgOpacity, borderRadius: elements.messageBorderRadius, padding: elements.messagePadding } },
{ selector: '.layout-hub-hot-title', elements: { fontSize: elements.hotTitleFontSize, fontWeight: elements.hotTitleFontWeight, color: elements.hotTitleColor, lineHeight: elements.hotTitleLineHeight, bgColor: elements.hotTitleBgColor, bgOpacity: elements.hotTitleBgOpacity, borderRadius: elements.hotTitleBorderRadius, padding: elements.hotTitlePadding } },
{ selector: '.layout-hub-hot-issues', elements: { fontSize: elements.hotIssuesFontSize, fontWeight: elements.hotIssuesFontWeight, color: elements.hotIssuesColor, lineHeight: elements.hotIssuesLineHeight, bgColor: elements.hotIssuesBgColor, bgOpacity: elements.hotIssuesBgOpacity, borderRadius: elements.hotIssuesBorderRadius, padding: elements.hotIssuesPadding } }
];
sections.forEach(section => {
const el = captionContainer.querySelector(section.selector);
if (!el) return;
const styles = window.getComputedStyle(el);
section.elements.fontSize.value = parseInt(styles.fontSize) || 14;
section.elements.fontWeight.value = styles.fontWeight === 'bold' ? 'bold' : 'normal';
section.elements.color.value = parseColor(styles.color);
section.elements.lineHeight.value = parseFloat(styles.lineHeight) || 1.5;
if (styles.backgroundColor && styles.backgroundColor !== 'transparent') {
const rgbaMatch = styles.backgroundColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
if (rgbaMatch) {
section.elements.bgColor.value = rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
section.elements.bgOpacity.value = rgbaMatch[4] ? Math.round(parseFloat(rgbaMatch[4]) * 100) : 100;
}
}
section.elements.borderRadius.value = parseInt(styles.borderRadius) || 4;
section.elements.padding.value = parseInt(styles.padding) || 6;
updateRangeValue(section.elements.bgOpacity);
});
logger.info('已从字幕容器加载样式');
}
function parseColor(colorStr) {
if (!colorStr) return '#ffffff';
if (colorStr.startsWith('#')) return colorStr;
const rgbaMatch = colorStr.match(/rgba?\((\d+),\s*(\d+),\s*(\d+),?\s*([\d.]*)\)/);
if (rgbaMatch) {
return rgbToHex(parseInt(rgbaMatch[1]), parseInt(rgbaMatch[2]), parseInt(rgbaMatch[3]));
}
return '#ffffff';
}
function rgbToHex(r, g, b) {
return '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
}
function updateRangeValue(rangeInput) {
const valueSpan = rangeInput.nextElementSibling;
if (valueSpan && valueSpan.classList.contains('range-value')) {
valueSpan.textContent = `${rangeInput.value}%`;
}
}
function initCaptionDragAndResize(container) {
container.dataset.dragInitialized = 'true';
@@ -524,6 +669,7 @@ function watchCaptionContainer() {
captionContainer.style.userSelect = 'none';
captionContainer.style.zIndex = '9999';
initCaptionDragAndResize(captionContainer);
loadCaptionStyleFromContainer();
}
}
}
@@ -534,6 +680,10 @@ function watchCaptionContainer() {
subtree: true
});
if (document.querySelector('.caption-container-hub')) {
setTimeout(loadCaptionStyleFromContainer, 100);
}
logger.info('已启动字幕容器监听');
}
@@ -593,6 +743,15 @@ function bindEvents() {
elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode);
elements.applyCaptionStyleBtn.addEventListener('click', applyCaptionStyle);
document.querySelectorAll('input[type="range"]').forEach(slider => {
slider.addEventListener('input', (e) => {
const valueSpan = e.target.nextElementSibling;
if (valueSpan && valueSpan.classList.contains('range-value')) {
valueSpan.textContent = `${e.target.value}%`;
}
});
});
elements.clearLogBtn.addEventListener('click', clearLogs);
elements.logLevel.addEventListener('change', () => {
if (state.isInitialized) {
@@ -602,7 +761,10 @@ function bindEvents() {
});
elements.toggleConfigBtn.addEventListener('click', () => togglePanel('configPanel'));
elements.toggleCaptionBtn.addEventListener('click', () => togglePanel('captionPanel'));
elements.toggleCaptionBtn.addEventListener('click', () => {
togglePanel('captionPanel');
loadCaptionStyleFromContainer();
});
elements.toggleLogBtn.addEventListener('click', () => togglePanel('logPanel'));
elements.fullscreenBtn.addEventListener('click', toggleFullscreen);

View File

@@ -373,6 +373,32 @@ body {
height: 32px;
}
.caption-style-section input[type="range"] {
width: calc(100% - 50px);
height: 6px;
border-radius: 3px;
background: var(--bg-tertiary);
outline: none;
-webkit-appearance: none;
margin-right: 8px;
}
.caption-style-section input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 16px;
height: 16px;
border-radius: 50%;
background: var(--primary-color);
cursor: pointer;
}
.caption-style-section .range-value {
font-size: 0.75rem;
color: var(--text-muted);
min-width: 35px;
display: inline-block;
}
/* ===== 日志面板 ===== */
.log-panel {
bottom: calc(var(--spacing-xl) + 60px);