Skip to content

Commit cd0bfa9

Browse files
authored
Merge pull request #3 from Albert-PZY/feat/bg-video-audio
背景视频声音与音量调节 + 弹窗不透明度保底
2 parents 9659fe5 + 4ca0690 commit cd0bfa9

5 files changed

Lines changed: 57 additions & 8 deletions

File tree

src/lib/app.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,33 @@ function bindBackTop(): void {
414414
});
415415
}
416416

417+
/**
418+
* 播放背景视频并按用户设定的音量输出声音。
419+
* 浏览器默认禁止带声音的视频自动播放,只有用户与页面交互后才允许。
420+
* 因此先尝试带声播放,被拦截时回退为静音自动播放,并在首次用户交互后自动恢复音量。
421+
*/
422+
function playBackgroundVideo(v: HTMLVideoElement, volume: number): void {
423+
const vol = Math.max(0, Math.min(1, volume / 100));
424+
v.volume = vol;
425+
const wantSound = vol > 0;
426+
v.muted = !wantSound;
427+
const tryPlay = () => v.play().catch(() => undefined);
428+
void v.play().catch(() => {
429+
if (!wantSound) return;
430+
v.muted = true;
431+
void tryPlay();
432+
const unmute = () => {
433+
v.muted = false;
434+
v.volume = vol;
435+
void tryPlay();
436+
window.removeEventListener('pointerdown', unmute);
437+
window.removeEventListener('keydown', unmute);
438+
};
439+
window.addEventListener('pointerdown', unmute, { once: true });
440+
window.addEventListener('keydown', unmute, { once: true });
441+
});
442+
}
443+
417444
/** 应用自定义背景:图 / GIF / 视频到 .bg-layer,统一自适应覆盖 */
418445
function applyBackground(bg: BackgroundConfig): void {
419446
const layer = document.getElementById('bg-layer');
@@ -422,7 +449,10 @@ function applyBackground(bg: BackgroundConfig): void {
422449
// 背景图自身不透明度
423450
layer.style.setProperty('--bg-opacity', bg.enabled && bg.src ? String(bg.opacity / 100) : '0');
424451
// 页面内容层不透明度:让顶栏/侧栏/卡片等表面层半透明,背景透出
425-
document.documentElement.style.setProperty('--page-opacity', bg.enabled && bg.src ? String(bg.pageOpacity / 100) : '1');
452+
const pageOpacity = bg.enabled && bg.src ? bg.pageOpacity / 100 : 1;
453+
document.documentElement.style.setProperty('--page-opacity', String(pageOpacity));
454+
// 模态框不透明度:独立于页面,保底 0.5,避免设置弹窗过透看不清
455+
document.documentElement.style.setProperty('--modal-opacity', String(Math.max(0.5, pageOpacity)));
426456

427457
if (!bg.enabled || !bg.src) {
428458
body.classList.remove('has-bg');
@@ -433,18 +463,18 @@ function applyBackground(bg: BackgroundConfig): void {
433463
body.classList.add('has-bg');
434464

435465
if (bg.mime.startsWith('video/')) {
436-
// 视频用 <video> 元素 autoplay loop muted
466+
// 视频用 <video> 元素 autoplay loop,音量由用户设置控制
437467
let v = layer.querySelector('video');
438468
if (!v) {
439469
layer.replaceChildren();
440470
v = document.createElement('video');
441471
v.autoplay = true;
442472
v.loop = true;
443-
v.muted = true;
444473
v.setAttribute('playsinline', '');
445474
layer.appendChild(v);
446475
}
447476
if (v.src !== bg.src) v.src = bg.src;
477+
playBackgroundVideo(v, bg.volume);
448478
layer.style.backgroundImage = '';
449479
} else {
450480
// 图 / GIF 用 background-image,统一 cover 覆盖

src/lib/types.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ export interface BackgroundConfig {
9393
posY: number;
9494
/** 媒体 MIME,用于判断图/视频 */
9595
mime: string;
96+
/** 背景视频音量 0-100,仅视频背景生效 */
97+
volume: number;
9698
}
9799

98100
export interface Settings {
@@ -128,4 +130,5 @@ export const DEFAULT_BACKGROUND: BackgroundConfig = {
128130
posX: 50,
129131
posY: 50,
130132
mime: '',
133+
volume: 50,
131134
};

src/lib/ui/dialogs.ts

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,17 @@ export async function openSettings(): Promise<void> {
447447
const pageOpacityWrap = el('div', { style: 'flex:1;min-width:160px;display:flex;flex-direction:column;gap:6px' });
448448
pageOpacityWrap.append(pageOpacityLabel, pageOpacityRange);
449449

450+
/* 视频音量:仅当背景为视频时显示 */
451+
const volumeLabel = el('div', { class: 'field__hint', text: `视频音量 ${store.background.volume}%` });
452+
const volumeRange = el('input', { class: 'range', type: 'range', min: '0', max: '100', value: String(store.background.volume) });
453+
volumeRange.addEventListener('input', () => {
454+
const v = Number(volumeRange.value);
455+
volumeLabel.textContent = `视频音量 ${v}%`;
456+
void store.setBackground({ volume: v });
457+
});
458+
const volumeWrap = el('div', { style: 'flex:1;min-width:160px;display:flex;flex-direction:column;gap:6px' });
459+
volumeWrap.append(volumeLabel, volumeRange);
460+
450461
const enToggle = toggle('启用背景', '', store.background.enabled, (v) => { void store.setBackground({ enabled: v }); });
451462

452463
const fileInput = el('input', { type: 'file', accept: 'image/*,video/*' });
@@ -457,8 +468,8 @@ export async function openSettings(): Promise<void> {
457468
fileInput.addEventListener('change', () => {
458469
const f = fileInput.files?.[0];
459470
if (!f) return;
460-
if (f.size > 10 * 1024 * 1024) {
461-
toast('文件不能超过 10MB', { kind: 'warn' });
471+
if (f.size > 100 * 1024 * 1024) {
472+
toast('文件不能超过 100MB', { kind: 'warn' });
462473
return;
463474
}
464475
if (!/^image\/|^video\//.test(f.type)) {
@@ -503,11 +514,11 @@ export async function openSettings(): Promise<void> {
503514
bgPreview.addEventListener('pointercancel', () => { dragging = false; });
504515

505516
const bgControls = el('div', { style: 'display:flex;gap:12px;align-items:flex-start;flex-wrap:wrap;margin-top:10px' });
506-
bgControls.append(bgOpacityWrap, pageOpacityWrap);
517+
bgControls.append(bgOpacityWrap, pageOpacityWrap, volumeWrap);
507518
const bgActions = el('div', { style: 'display:flex;gap:8px;align-items:center;flex-wrap:wrap;margin-top:8px' });
508519
bgActions.append(enToggle, uploadBtn, clearBtn);
509520
bgField.append(bgPreview, bgControls, bgActions, fileInput);
510-
bgField.appendChild(el('p', { class: 'field__hint', text: '图片 / GIF / 视频 ≤10MB,拖动预览可调位置' }));
521+
bgField.appendChild(el('p', { class: 'field__hint', text: '图片 / GIF / 视频 ≤100MB,拖动预览可调位置' }));
511522
m.body.appendChild(bgField);
512523

513524
// 背景预览重绘
@@ -521,6 +532,9 @@ export async function openSettings(): Promise<void> {
521532
bgOpacityLabel.textContent = `背景不透明度 ${bg.opacity}%`;
522533
pageOpacityRange.value = String(bg.pageOpacity);
523534
pageOpacityLabel.textContent = `页面不透明度 ${bg.pageOpacity}%`;
535+
volumeRange.value = String(bg.volume);
536+
volumeLabel.textContent = `视频音量 ${bg.volume}%`;
537+
volumeWrap.style.display = bg.mime.startsWith('video/') ? '' : 'none';
524538
};
525539
paintBg();
526540

src/styles/_overlays.scss

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
flex-direction: column;
3838
width: min(100%, var(--modal-w, 540px));
3939
max-height: min(86vh, 800px);
40-
background: color-mix(in srgb, var(--surface), transparent calc((1 - var(--page-opacity)) * 100%));
40+
background: color-mix(in srgb, var(--surface), transparent calc((1 - var(--modal-opacity)) * 100%));
4141
backdrop-filter: saturate(1.6) blur(20px);
4242
-webkit-backdrop-filter: saturate(1.6) blur(20px);
4343
border: 1px solid var(--border);

src/styles/_tokens.scss

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141

4242
/* 页面内容层不透明度:背景启用时由 app.ts 设为 <1,让背景透出 */
4343
--page-opacity: 1;
44+
/* 模态框不透明度:独立于页面不透明度,保底 0.5,避免设置弹窗过透看不清 */
45+
--modal-opacity: 1;
4446

4547
/* 动效曲线:分快速交互 / 标准 / 慢速大动画三档,全部 GPU 友好 */
4648
--ease: cubic-bezier(0.22, 0.68, 0.32, 1);

0 commit comments

Comments
 (0)