H5多功能面板
(function () {
if (document.getElementById("custom-float-panel")) return;
let isCollapsed = false;
function createFloatPanel() {
const panel = document.createElement("div");
panel.id = "custom-float-panel";
panel.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
z-index: 99999;
background: #fff;
padding: 20px;
border: 1px solid #e5e7eb;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
min-width: 350px;
max-height: 80vh; /* 限制最大高度为视口80% */
overflow: hidden; /* 隐藏溢出,由内容区滚动 */
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
transition: all 0.3s ease;
`;
const panelHeader = document.createElement("div");
panelHeader.style.cssText = `
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
position: sticky;
top: 0;
background: #fff;
z-index: 1;
padding-bottom: 8px;
`;
const title = document.createElement("h3");
title.textContent = "多功能面板";
title.style.cssText = `
margin: 0;
font-size: 18px;
color: #1f2937;
font-weight: 600;
white-space: nowrap;
`;
const toggleBtn = document.createElement("button");
toggleBtn.id = "panel-toggle-btn";
toggleBtn.textContent = "−";
toggleBtn.style.cssText = `
width: 24px;
height: 24px;
border: none;
border-radius: 4px;
background: #f3f4f6;
color: #4b5563;
cursor: pointer;
font-size: 16px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: 0 1px 2px rgba(0,0,0,0.05);
transition: background 0.2s;
`;
toggleBtn.onmouseover = () => toggleBtn.style.background = "#e5e7eb";
toggleBtn.onmouseout = () => toggleBtn.style.background = "#f3f4f6";
panelHeader.appendChild(title);
panelHeader.appendChild(toggleBtn);
// ========== 核心修改:内容区增加滚动 ==========
const panelContent = document.createElement("div");
panelContent.id = "panel-content";
panelContent.style.cssText = `
overflow-y: auto; /* 垂直滚动 */
max-height: calc(80vh - 80px); /* 减去头部高度,适配最大高度 */
padding-right: 4px; /* 给滚动条预留空间 */
scrollbar-width: thin; /* 精简滚动条(Firefox) */
scrollbar-color: #d1d5db #f9fafb; /* 滚动条样式 */
`;
// 自定义滚动条样式(Chrome/Safari)
panelContent.style.cssText += `
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: #f9fafb;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #d1d5db;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb:hover {
background: #9ca3af;
}
`;
// 分割线
const divider = document.createElement("hr");
divider.style.cssText = `
border: none;
border-top: 1px solid #e5e7eb;
margin: 14px 0;
`;
// ========== 通用按钮样式(复用) ==========
const btnCommonStyle = `
padding: 8px 16px;
border: 1px solid transparent;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
box-shadow: 0 2px 4px rgba(0,0,0,0.05);
transition: all 0.2s ease;
`;
// ========== 定位模块 ==========
const geoSection = document.createElement("div");
geoSection.style.marginBottom = "20px";
const geoTitle = document.createElement("div");
geoTitle.textContent = "地理位置";
geoTitle.style.cssText = `
font-weight: 600;
margin-bottom: 10px;
color: #4b5563;
font-size: 14px;
`;
const geoBtn = document.createElement("button");
geoBtn.textContent = "获取位置";
geoBtn.style.cssText = `${btnCommonStyle}
background: #3b82f6;
color: white;
border-color: #2563eb;
`;
geoBtn.onmouseover = () => {
geoBtn.style.background = "#2563eb";
geoBtn.style.boxShadow = "0 4px 8px rgba(59, 130, 246, 0.2)";
};
geoBtn.onmouseout = () => {
geoBtn.style.background = "#3b82f6";
geoBtn.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
};
const geoResult = document.createElement("div");
geoResult.style.cssText = `
margin-top: 8px;
font-size: 12px;
color: #6b7280;
min-height: 20px;
`;
geoResult.textContent = "未获取";
geoSection.appendChild(geoTitle);
geoSection.appendChild(geoBtn);
geoSection.appendChild(geoResult);
// ========== 文件选择模块 ==========
const fileSection = document.createElement("div");
fileSection.style.marginBottom = "20px";
const fileTitle = document.createElement("div");
fileTitle.textContent = "多文件选择";
fileTitle.style.cssText = `
font-weight: 600;
margin-bottom: 10px;
color: #4b5563;
font-size: 14px;
`;
const fileBtn = document.createElement("button");
fileBtn.textContent = "选择文件";
fileBtn.style.cssText = `${btnCommonStyle}
background: #10b981;
color: white;
border-color: #059669;
`;
fileBtn.onmouseover = () => {
fileBtn.style.background = "#059669";
fileBtn.style.boxShadow = "0 4px 8px rgba(16, 185, 129, 0.2)";
};
fileBtn.onmouseout = () => {
fileBtn.style.background = "#10b981";
fileBtn.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
};
const fileInput = document.createElement("input");
fileInput.type = "file";
fileInput.multiple = true;
fileInput.style.display = "none";
const fileInfo = document.createElement("div");
fileInfo.style.cssText = `
margin-top: 8px;
font-size: 12px;
color: #6b7280;
min-height: 20px;
`;
fileInfo.textContent = "未选择";
fileSection.appendChild(fileTitle);
fileSection.appendChild(fileBtn);
fileSection.appendChild(fileInput);
fileSection.appendChild(fileInfo);
// ========== 录音模块 ==========
const audioSection = document.createElement("div");
audioSection.style.marginBottom = "20px";
const audioTitle = document.createElement("div");
audioTitle.textContent = "H5录音";
audioTitle.style.cssText = `
font-weight: 600;
margin-bottom: 10px;
color: #4b5563;
font-size: 14px;
`;
const audioBtnWrap = document.createElement("div");
audioBtnWrap.style.display = "flex";
audioBtnWrap.style.gap = "8px";
const startAudio = document.createElement("button");
startAudio.textContent = "开始录音";
startAudio.style.cssText = `${btnCommonStyle}
background: #f59e0b;
color: white;
border-color: #d97706;
`;
startAudio.onmouseover = () => {
startAudio.style.background = "#d97706";
startAudio.style.boxShadow = "0 4px 8px rgba(245, 158, 11, 0.2)";
};
startAudio.onmouseout = () => {
startAudio.style.background = "#f59e0b";
startAudio.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
};
const stopAudio = document.createElement("button");
stopAudio.textContent = "停止录音";
stopAudio.style.cssText = `${btnCommonStyle}
background: #ef4444;
color: white;
border-color: #dc2626;
opacity: 0.6;
pointer-events: none;
`;
stopAudio.onmouseover = () => {
if (!stopAudio.disabled) {
stopAudio.style.background = "#dc2626";
stopAudio.style.boxShadow = "0 4px 8px rgba(239, 68, 68, 0.2)";
}
};
stopAudio.onmouseout = () => {
if (!stopAudio.disabled) {
stopAudio.style.background = "#ef4444";
stopAudio.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
}
};
const audioResult = document.createElement("div");
audioResult.style.cssText = `
margin-top: 8px;
font-size: 12px;
color: #6b7280;
min-height: 20px;
`;
audioResult.textContent = "未录音";
audioBtnWrap.appendChild(startAudio);
audioBtnWrap.appendChild(stopAudio);
audioSection.appendChild(audioTitle);
audioSection.appendChild(audioBtnWrap);
audioSection.appendChild(audioResult);
// ========== 录像模块(优化显示) ==========
const videoSection = document.createElement("div");
videoSection.style.marginBottom = "20px"; // 增加底部间距
const videoTitle = document.createElement("div");
videoTitle.textContent = "H5录像";
videoTitle.style.cssText = `
font-weight: 600;
margin-bottom: 10px;
color: #4b5563;
font-size: 14px;
`;
const videoBtnWrap = document.createElement("div");
videoBtnWrap.style.display = "flex";
videoBtnWrap.style.gap = "8px";
const startVideo = document.createElement("button");
startVideo.textContent = "开始录像";
startVideo.style.cssText = `${btnCommonStyle}
background: #8b5cf6;
color: white;
border-color: #7c3aed;
`;
startVideo.onmouseover = () => {
startVideo.style.background = "#7c3aed";
startVideo.style.boxShadow = "0 4px 8px rgba(139, 92, 246, 0.2)";
};
startVideo.onmouseout = () => {
startVideo.style.background = "#8b5cf6";
startVideo.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
};
const stopVideo = document.createElement("button");
stopVideo.textContent = "停止录像";
stopVideo.style.cssText = `${btnCommonStyle}
background: #ef4444;
color: white;
border-color: #dc2626;
opacity: 0.6;
pointer-events: none;
`;
stopVideo.onmouseover = () => {
if (!stopVideo.disabled) {
stopVideo.style.background = "#dc2626";
stopVideo.style.boxShadow = "0 4px 8px rgba(239, 68, 68, 0.2)";
}
};
stopVideo.onmouseout = () => {
if (!stopVideo.disabled) {
stopVideo.style.background = "#ef4444";
stopVideo.style.boxShadow = "0 2px 4px rgba(0,0,0,0.05)";
}
};
// ========== 核心优化:录像预览区域 ==========
const videoPreview = document.createElement("div");
videoPreview.style.cssText = `
margin-top: 8px;
font-size: 12px;
color: #6b7280;
min-height: 20px;
width: 100%; /* 宽度100%适配面板 */
`;
// 视频元素专属样式
videoPreview.innerHTML = `
<style>
#video-preview-container video {
max-width: 100% !important;
height: auto !important;
border-radius: 6px;
margin-top: 8px;
}
</style>
<div id="video-preview-container">未录像</div>
`;
videoBtnWrap.appendChild(startVideo);
videoBtnWrap.appendChild(stopVideo);
videoSection.appendChild(videoTitle);
videoSection.appendChild(videoBtnWrap);
videoSection.appendChild(videoPreview);
// 组装内容
panelContent.appendChild(geoSection);
panelContent.appendChild(fileSection);
panelContent.appendChild(audioSection);
panelContent.appendChild(videoSection);
panel.appendChild(panelHeader);
panel.appendChild(divider);
panel.appendChild(panelContent);
document.body.appendChild(panel);
return {
panel, toggleBtn, panelContent, title,
geoBtn, geoResult,
fileBtn, fileInput, fileInfo,
startAudio, stopAudio, audioResult,
startVideo, stopVideo,
videoPreview: document.getElementById("video-preview-container")
};
}
// 折叠/展开:正方形 + 横排缩写
function initToggle(dom) {
dom.toggleBtn.onclick = () => {
isCollapsed = !isCollapsed;
if (isCollapsed) {
// 折叠状态:60×60 正方形
dom.panel.style.minWidth = "60px";
dom.panel.style.width = "60px";
dom.panel.style.height = "60px";
dom.panel.style.padding = "8px";
dom.panel.style.borderRadius = "12px";
dom.panelContent.style.display = "none";
dom.toggleBtn.textContent = "+";
// 标题缩写 + 缩小字号
dom.title.textContent = "多功能";
dom.title.style.fontSize = "12px";
dom.title.style.maxWidth = "32px";
dom.title.style.overflow = "hidden";
dom.title.style.textOverflow = "ellipsis";
} else {
// 展开状态:恢复原样式
dom.panel.style.minWidth = "350px";
dom.panel.style.width = "";
dom.panel.style.height = "";
dom.panel.style.padding = "20px";
dom.panelContent.style.display = "block";
dom.toggleBtn.textContent = "−";
dom.title.textContent = "多功能面板";
dom.title.style.fontSize = "18px";
dom.title.style.maxWidth = "none";
dom.title.style.overflow = "visible";
}
};
}
// 定位功能
function initGeo(dom) {
dom.geoBtn.onclick = () => {
if (!navigator.geolocation) {
dom.geoResult.textContent = "不支持定位";
return;
}
dom.geoResult.textContent = "获取中...";
navigator.geolocation.getCurrentPosition(pos => {
const lat = pos.coords.latitude.toFixed(6);
const lng = pos.coords.longitude.toFixed(6);
dom.geoResult.textContent = `纬度:${lat},经度:${lng}`;
}, err => {
dom.geoResult.textContent = "获取失败:" + err.message;
});
};
}
// 文件选择功能
function initFile(dom) {
dom.fileBtn.onclick = () => dom.fileInput.click();
dom.fileInput.onchange = () => {
const files = Array.from(dom.fileInput.files);
if (files.length === 0) {
dom.fileInfo.textContent = "未选择";
return;
}
dom.fileInfo.innerHTML = `已选 ${files.length} 个文件`;
dom.fileInput.value = "";
};
}
// 录音功能
let audioRecorder;
function initAudio(dom) {
dom.startAudio.onclick = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
audioRecorder = new MediaRecorder(stream);
const chunks = [];
audioRecorder.ondataavailable = e => chunks.push(e.data);
audioRecorder.onstop = () => {
const blob = new Blob(chunks, { type: "audio/webm" });
const url = URL.createObjectURL(blob);
dom.audioResult.innerHTML = `
录音完成<br>
<audio controls src="${url}" style="width:100%;margin-top:8px;">
`;
// 恢复按钮状态
dom.startAudio.style.opacity = "1";
dom.startAudio.style.pointerEvents = "auto";
dom.stopAudio.style.opacity = "0.6";
dom.stopAudio.style.pointerEvents = "none";
// 停止音轨
stream.getTracks().forEach(t => t.stop());
};
audioRecorder.start();
dom.audioResult.textContent = "录音中...";
// 禁用开始按钮,启用停止按钮
dom.startAudio.style.opacity = "0.6";
dom.startAudio.style.pointerEvents = "none";
dom.stopAudio.style.opacity = "1";
dom.stopAudio.style.pointerEvents = "auto";
} catch (err) {
dom.audioResult.textContent = "录音失败:" + err.message;
}
};
dom.stopAudio.onclick = () => {
audioRecorder?.stop();
};
}
// 录像功能(优化显示)
let videoRecorder;
function initVideo(dom) {
dom.startVideo.onclick = async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
videoRecorder = new MediaRecorder(stream);
const chunks = [];
videoRecorder.ondataavailable = e => chunks.push(e.data);
videoRecorder.onstop = () => {
const blob = new Blob(chunks, { type: "video/webm" });
const url = URL.createObjectURL(blob);
// 优化视频显示:宽度100%,高度自适应
dom.videoPreview.innerHTML = `
录像完成(大小:${Math.round(blob.size/1024)}KB)<br>
<video controls src="${url}" style="width:100%;height:auto;">
`;
// 恢复按钮状态
dom.startVideo.style.opacity = "1";
dom.startVideo.style.pointerEvents = "auto";
dom.stopVideo.style.opacity = "0.6";
dom.stopVideo.style.pointerEvents = "none";
// 停止音视频轨
stream.getTracks().forEach(t => t.stop());
// 滚动到录像区域(可选,提升体验)
dom.videoPreview.scrollIntoView({ behavior: "smooth", block: "center" });
};
videoRecorder.start();
dom.videoPreview.textContent = "录像中...";
// 禁用开始按钮,启用停止按钮
dom.startVideo.style.opacity = "0.6";
dom.startVideo.style.pointerEvents = "none";
dom.stopVideo.style.opacity = "1";
dom.stopVideo.style.pointerEvents = "auto";
} catch (err) {
dom.videoPreview.textContent = "录像失败:" + err.message;
}
};
dom.stopVideo.onclick = () => {
videoRecorder?.stop();
};
}
// 启动所有功能
const dom = createFloatPanel();
initToggle(dom);
initGeo(dom);
initFile(dom);
initAudio(dom);
initVideo(dom);
})();