From d2cd381bc2b4dbdc30f2b0c20a998539dec2a5ca Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Wed, 24 Sep 2025 19:16:47 +0800 Subject: [PATCH 1/6] Initial commit --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..817d68c --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# ticiqi +两种提词器网页的源码,包含文本提词器和pdf提词器,支持直接在本地使用,但是可能需要联网。 From bce6681495e9280bba5366d9638165de991802b2 Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Wed, 24 Sep 2025 19:17:03 +0800 Subject: [PATCH 2/6] Add files via upload --- pdf提词器/index.html | 88 +++++ pdf提词器/script.js | 408 ++++++++++++++++++++++ pdf提词器/styles.css | 407 ++++++++++++++++++++++ 文本提词器/index.html | 130 +++++++ 文本提词器/script.js | 772 ++++++++++++++++++++++++++++++++++++++++++ 文本提词器/styles.css | 472 ++++++++++++++++++++++++++ 6 files changed, 2277 insertions(+) create mode 100644 pdf提词器/index.html create mode 100644 pdf提词器/script.js create mode 100644 pdf提词器/styles.css create mode 100644 文本提词器/index.html create mode 100644 文本提词器/script.js create mode 100644 文本提词器/styles.css diff --git a/pdf提词器/index.html b/pdf提词器/index.html new file mode 100644 index 0000000..ebbcc69 --- /dev/null +++ b/pdf提词器/index.html @@ -0,0 +1,88 @@ + + + + + + PDF提词器 + + + +
+
+

PDF提词器

+

上传PDF文件,自动生成镜像提词效果

+
+ +
+
+ + + + + +

点击或拖拽PDF文件到此处

+ +
+
+ + + + + + + + +
+ + + + + \ No newline at end of file diff --git a/pdf提词器/script.js b/pdf提词器/script.js new file mode 100644 index 0000000..c81e4c9 --- /dev/null +++ b/pdf提词器/script.js @@ -0,0 +1,408 @@ +class PDFTeleprompter { + constructor() { + this.pdfDoc = null; + this.pages = []; + this.isPlaying = false; + this.animationId = null; + this.scrollPosition = 0; + this.scrollSpeed = 2; + + this.initializeElements(); + this.bindEvents(); + } + + initializeElements() { + this.uploadArea = document.getElementById('uploadArea'); + this.fileInput = document.getElementById('fileInput'); + this.controls = document.getElementById('controls'); + this.displayArea = document.getElementById('displayArea'); + this.teleprompterContent = document.getElementById('teleprompterContent'); + this.loading = document.getElementById('loading'); + this.speedSlider = document.getElementById('speedSlider'); + this.speedValue = document.getElementById('speedValue'); + this.playBtn = document.getElementById('playBtn'); + this.pauseBtn = document.getElementById('pauseBtn'); + this.resetBtn = document.getElementById('resetBtn'); + this.fullscreenBtn = document.getElementById('fullscreenBtn'); + + // 悬浮控制面板元素 + this.floatingControls = document.getElementById('floatingControls'); + this.floatPlayBtn = document.getElementById('floatPlayBtn'); + this.floatResetBtn = document.getElementById('floatResetBtn'); + this.floatExitBtn = document.getElementById('floatExitBtn'); + this.floatSpeedSlider = document.getElementById('floatSpeedSlider'); + this.floatSpeedValue = document.getElementById('floatSpeedValue'); + + // 滚动容器 + this.teleprompterContainer = document.querySelector('.teleprompter-container'); + } + + bindEvents() { + this.uploadArea.addEventListener('click', () => this.fileInput.click()); + this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e)); + this.uploadArea.addEventListener('dragover', (e) => this.handleDragOver(e)); + this.uploadArea.addEventListener('drop', (e) => this.handleDrop(e)); + + this.speedSlider.addEventListener('input', (e) => { + this.scrollSpeed = parseFloat(e.target.value); + this.speedValue.textContent = this.scrollSpeed; + if (this.floatSpeedSlider) { + this.floatSpeedSlider.value = this.scrollSpeed; + this.floatSpeedValue.textContent = this.scrollSpeed; + } + }); + + this.playBtn.addEventListener('click', () => this.play()); + this.pauseBtn.addEventListener('click', () => this.pause()); + this.resetBtn.addEventListener('click', () => this.reset()); + this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); + + // 悬浮控制面板事件 + if (this.floatPlayBtn) { + this.floatPlayBtn.addEventListener('click', () => this.togglePlayPause()); + this.floatResetBtn.addEventListener('click', () => this.reset()); + this.floatExitBtn.addEventListener('click', () => this.exitFullscreen()); + + this.floatSpeedSlider.addEventListener('input', (e) => { + this.scrollSpeed = parseFloat(e.target.value); + this.floatSpeedValue.textContent = this.scrollSpeed; + this.speedSlider.value = this.scrollSpeed; + this.speedValue.textContent = this.scrollSpeed; + }); + } + + // 全屏状态变化监听 + ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'].forEach(event => { + document.addEventListener(event, () => this.handleFullscreenChange()); + }); + + // 手动滚动监听 + this.teleprompterContainer.addEventListener('scroll', () => { + if (!this.isPlaying) { + this.scrollPosition = this.teleprompterContainer.scrollTop; + } + }); + + // 防止滚动时自动播放 + this.teleprompterContainer.addEventListener('mousedown', () => { + if (this.isPlaying) { + this.pause(); + } + }); + + // 键盘快捷键 + document.addEventListener('keydown', (e) => { + if (e.key === ' ' || e.key === 'Spacebar') { + e.preventDefault(); + this.togglePlayPause(); + } else if (e.key === 'f' || e.key === 'F') { + e.preventDefault(); + this.toggleFullscreen(); + } else if (e.key === 'Escape') { + if (document.fullscreenElement) { + this.exitFullscreen(); + } + } + }); + + // 触摸手势支持 + let touchStartY = 0; + this.teleprompterContainer.addEventListener('touchstart', (e) => { + touchStartY = e.touches[0].clientY; + if (this.isPlaying) { + this.pause(); + } + }); + + this.teleprompterContainer.addEventListener('touchmove', (e) => { + e.preventDefault(); + const touchY = e.touches[0].clientY; + const deltaY = touchStartY - touchY; + this.teleprompterContainer.scrollTop += deltaY; + touchStartY = touchY; + this.scrollPosition = this.teleprompterContainer.scrollTop; + }); + } + + async handleFileUpload(event) { + const file = event.target.files[0]; + if (file && file.type === 'application/pdf') { + await this.processPDF(file); + } + } + + async processPDF(file) { + this.showLoading(true); + + try { + const arrayBuffer = await file.arrayBuffer(); + this.pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; + + await this.renderAllPages(); + this.showControls(); + + } catch (error) { + console.error('Error processing PDF:', error); + alert('处理PDF文件时出错,请重试'); + } finally { + this.showLoading(false); + } + } + + async renderAllPages() { + this.pages = []; + this.teleprompterContent.innerHTML = ''; + + const totalPages = this.pdfDoc.numPages; + + for (let pageNum = 1; pageNum <= totalPages; pageNum++) { + const page = await this.pdfDoc.getPage(pageNum); + const canvas = await this.renderPageToCanvas(page); + + const img = document.createElement('img'); + img.src = canvas.toDataURL('image/png'); + img.style.width = '100%'; + img.style.height = 'auto'; + img.style.marginBottom = '20px'; + + this.teleprompterContent.appendChild(img); + this.pages.push(img); + } + } + + async renderPageToCanvas(page) { + const viewport = page.getViewport({ scale: 1.5 }); + const canvas = document.createElement('canvas'); + const context = canvas.getContext('2d'); + + canvas.width = viewport.width; + canvas.height = viewport.height; + + const renderContext = { + canvasContext: context, + viewport: viewport + }; + + await page.render(renderContext).promise; + return canvas; + } + + showLoading(show) { + this.loading.style.display = show ? 'block' : 'none'; + } + + showControls() { + this.uploadArea.parentElement.style.display = 'none'; + this.controls.style.display = 'flex'; + this.displayArea.style.display = 'block'; + } + + play() { + if (!this.isPlaying) { + this.isPlaying = true; + this.playBtn.disabled = true; + this.pauseBtn.disabled = false; + this.animateScroll(); + } + } + + pause() { + this.isPlaying = false; + this.playBtn.disabled = false; + this.pauseBtn.disabled = true; + if (this.animationId) { + cancelAnimationFrame(this.animationId); + } + } + + reset() { + this.pause(); + this.scrollPosition = 0; + this.teleprompterContainer.scrollTop = 0; + } + + animateScroll() { + if (!this.isPlaying) return; + + const contentHeight = this.teleprompterContent.scrollHeight; + const containerHeight = this.teleprompterContainer.offsetHeight; + + this.scrollPosition += this.scrollSpeed; + + if (this.scrollPosition > contentHeight) { + this.scrollPosition = 0; + } + + this.teleprompterContainer.scrollTop = this.scrollPosition; + + this.animationId = requestAnimationFrame(() => this.animateScroll()); + } + + toggleFullscreen() { + if (!document.fullscreenElement) { + this.enterFullscreen(); + } else { + this.exitFullscreen(); + } + } + + enterFullscreen() { + const element = this.displayArea; + + if (element.requestFullscreen) { + element.requestFullscreen(); + } else if (element.webkitRequestFullscreen) { + element.webkitRequestFullscreen(); + } else if (element.msRequestFullscreen) { + element.msRequestFullscreen(); + } else if (element.mozRequestFullScreen) { + element.mozRequestFullScreen(); + } + } + + exitFullscreen() { + if (document.exitFullscreen) { + document.exitFullscreen(); + } else if (document.webkitExitFullscreen) { + document.webkitExitFullscreen(); + } else if (document.msExitFullscreen) { + document.msExitFullscreen(); + } else if (document.mozCancelFullScreen) { + document.mozCancelFullScreen(); + } + } + + handleFullscreenChange() { + const isFullscreen = !!( + document.fullscreenElement || + document.webkitFullscreenElement || + document.mozFullScreenElement || + document.msFullscreenElement + ); + + if (isFullscreen) { + this.displayArea.classList.add('fullscreen'); + document.body.classList.add('fullscreen'); + this.floatingControls.style.display = 'flex'; + this.fullscreenBtn.innerHTML = ` + + + + 退出全屏 + `; + } else { + this.displayArea.classList.remove('fullscreen'); + document.body.classList.remove('fullscreen'); + this.floatingControls.style.display = 'none'; + this.fullscreenBtn.innerHTML = ` + + + + 全屏 + `; + } + } + + togglePlayPause() { + if (this.isPlaying) { + this.pause(); + this.updatePlayButtons(false); + } else { + this.play(); + this.updatePlayButtons(true); + } + } + + updatePlayButtons(isPlaying) { + const playIcon = ` + + + + `; + const pauseIcon = ` + + + + + `; + + if (isPlaying) { + this.floatPlayBtn.innerHTML = pauseIcon; + } else { + this.floatPlayBtn.innerHTML = playIcon; + } + } + + play() { + if (!this.isPlaying) { + this.isPlaying = true; + this.playBtn.disabled = true; + this.pauseBtn.disabled = false; + this.updatePlayButtons(true); + this.animateScroll(); + } + } + + pause() { + this.isPlaying = false; + this.playBtn.disabled = false; + this.pauseBtn.disabled = true; + this.updatePlayButtons(false); + if (this.animationId) { + cancelAnimationFrame(this.animationId); + } + } + + // 键盘快捷键 + setupKeyboardShortcuts() { + document.addEventListener('keydown', (e) => { + switch(e.code) { + case 'Space': + e.preventDefault(); + if (this.isPlaying) { + this.pause(); + } else { + this.play(); + } + break; + case 'KeyR': + if (e.ctrlKey || e.metaKey) { + e.preventDefault(); + this.reset(); + } + break; + } + }); + } +} + +// 初始化应用 +document.addEventListener('DOMContentLoaded', () => { + const teleprompter = new PDFTeleprompter(); + teleprompter.setupKeyboardShortcuts(); +}); + +// 添加触摸手势支持 +let touchStartY = 0; +document.addEventListener('touchstart', (e) => { + touchStartY = e.touches[0].clientY; +}); + +document.addEventListener('touchend', (e) => { + const touchEndY = e.changedTouches[0].clientY; + const diff = touchStartY - touchEndY; + + if (Math.abs(diff) > 50) { + // 滑动控制滚动速度 + const speedSlider = document.getElementById('speedSlider'); + const currentSpeed = parseInt(speedSlider.value); + + if (diff > 0 && currentSpeed < 10) { + speedSlider.value = currentSpeed + 1; + } else if (diff < 0 && currentSpeed > 1) { + speedSlider.value = currentSpeed - 1; + } + + speedSlider.dispatchEvent(new Event('input')); + } +}); \ No newline at end of file diff --git a/pdf提词器/styles.css b/pdf提词器/styles.css new file mode 100644 index 0000000..7a3fb7e --- /dev/null +++ b/pdf提词器/styles.css @@ -0,0 +1,407 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; +} + +.container { + max-width: 1200px; + margin: 0 auto; + padding: 20px; +} + +header { + text-align: center; + margin-bottom: 40px; + color: white; +} + +h1 { + font-size: 2.5rem; + margin-bottom: 10px; + font-weight: 300; +} + +.subtitle { + font-size: 1.1rem; + opacity: 0.9; +} + +.upload-section { + margin-bottom: 30px; +} + +.upload-area { + border: 2px dashed rgba(255, 255, 255, 0.5); + border-radius: 15px; + padding: 60px 20px; + text-align: center; + cursor: pointer; + transition: all 0.3s ease; + background: rgba(255, 255, 255, 0.1); + backdrop-filter: blur(10px); +} + +.upload-area:hover { + border-color: rgba(255, 255, 255, 0.8); + background: rgba(255, 255, 255, 0.2); + transform: translateY(-2px); +} + +.upload-area.dragover { + border-color: white; + background: rgba(255, 255, 255, 0.3); + transform: scale(1.02); +} + +.upload-icon { + width: 60px; + height: 60px; + color: white; + margin-bottom: 15px; +} + +.upload-text { + color: white; + font-size: 1.2rem; + font-weight: 300; +} + +.controls { + background: white; + border-radius: 15px; + padding: 30px; + margin-bottom: 30px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 20px; +} + +.control-group { + display: flex; + align-items: center; + gap: 15px; +} + +.control-group label { + font-weight: 500; + color: #555; +} + +input[type="range"] { + width: 150px; + height: 6px; + border-radius: 3px; + background: #ddd; + outline: none; + -webkit-appearance: none; +} + +input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: #667eea; + cursor: pointer; +} + +#speedValue { + font-weight: bold; + color: #667eea; + min-width: 20px; +} + +.btn { + padding: 12px 24px; + border: none; + border-radius: 8px; + font-size: 1rem; + cursor: pointer; + transition: all 0.3s ease; + font-weight: 500; +} + +.btn.primary { + background: #667eea; + color: white; +} + +.btn.primary:hover { + background: #5a6fd8; + transform: translateY(-1px); +} + +.btn.secondary { + background: #f1f3f4; + color: #333; +} + +.btn.secondary:hover:not(:disabled) { + background: #e8eaed; + transform: translateY(-1px); +} + +.btn:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +.display-area { + background: white; + border-radius: 15px; + overflow: hidden; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); + margin: 20px; + max-height: 70vh; +} + +.teleprompter-container { + height: 500px; + overflow-y: auto; + overflow-x: hidden; + position: relative; + background: #000; + cursor: grab; + scrollbar-width: thin; + scrollbar-color: #666 #222; +} + +.teleprompter-container::-webkit-scrollbar { + width: 8px; +} + +.teleprompter-container::-webkit-scrollbar-track { + background: #222; +} + +.teleprompter-container::-webkit-scrollbar-thumb { + background: #666; + border-radius: 4px; +} + +.teleprompter-container::-webkit-scrollbar-thumb:hover { + background: #888; +} + +.teleprompter-container:active { + cursor: grabbing; +} + +.teleprompter-content { + padding: 20px; + transform: scaleX(-1); + transition: transform 0.3s ease; + min-height: 100%; +} + +.teleprompter-content img { + width: 100%; + height: auto; + display: block; + margin-bottom: 20px; + border-radius: 8px; + box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); +} + +/* 全屏模式无边框 */ +.display-area.fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 999; + border-radius: 0; + margin: 0; + max-height: none; +} + +.display-area.fullscreen .teleprompter-container { + height: 100vh; + border-radius: 0; + margin: 0; + padding: 0; +} + +.display-area.fullscreen .teleprompter-content { + padding: 0; +} + +.display-area.fullscreen .teleprompter-content img { + border-radius: 0; + box-shadow: none; + margin-bottom: 0; +} + +/* 滚动提示 */ +.scroll-indicator { + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-50%); + background: rgba(0, 0, 0, 0.5); + color: white; + padding: 8px 12px; + border-radius: 20px; + font-size: 12px; + opacity: 0; + transition: opacity 0.3s ease; + pointer-events: none; + z-index: 10; +} + +.teleprompter-container:hover .scroll-indicator { + opacity: 1; +} + +.loading { + text-align: center; + color: white; + padding: 40px; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid rgba(255, 255, 255, 0.3); + border-top: 4px solid white; + border-radius: 50%; + animation: spin 1s linear infinite; + margin: 0 auto 20px; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +.btn-icon { + width: 16px; + height: 16px; + margin-right: 8px; + vertical-align: middle; +} + +.floating-controls { + position: fixed; + top: 20px; + right: 20px; + background: rgba(0, 0, 0, 0.8); + backdrop-filter: blur(10px); + border-radius: 15px; + padding: 15px; + display: flex; + align-items: center; + gap: 10px; + z-index: 1000; + transition: all 0.3s ease; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); +} + +.floating-controls:hover { + background: rgba(0, 0, 0, 0.9); + transform: translateY(-2px); +} + +.float-btn { + width: 45px; + height: 45px; + border: none; + border-radius: 50%; + background: rgba(255, 255, 255, 0.2); + color: white; + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: all 0.3s ease; + backdrop-filter: blur(5px); +} + +.float-btn:hover { + background: rgba(255, 255, 255, 0.3); + transform: scale(1.1); +} + +.float-btn svg { + width: 20px; + height: 20px; +} + +.float-speed-control { + display: flex; + align-items: center; + gap: 8px; + padding: 0 10px; +} + +.float-speed-control input[type="range"] { + width: 100px; + height: 4px; + background: rgba(255, 255, 255, 0.3); +} + +.float-speed-control input[type="range"]::-webkit-slider-thumb { + width: 14px; + height: 14px; + background: white; +} + +#floatSpeedValue { + color: white; + font-size: 0.9rem; + font-weight: bold; + min-width: 25px; +} + +/* 全屏模式样式 */ +.display-area.fullscreen { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + z-index: 999; + border-radius: 0; + margin: 0; +} + +.display-area.fullscreen .teleprompter-container { + height: 100vh; + border-radius: 0; +} + +/* 全屏时隐藏原控制面板 */ +body.fullscreen .controls { + display: none !important; +} + +@media (max-width: 768px) { + .controls { + flex-direction: column; + align-items: stretch; + } + + .control-group { + justify-content: center; + } + + h1 { + font-size: 2rem; + } + + .teleprompter-container { + height: 400px; + } +} \ No newline at end of file diff --git a/文本提词器/index.html b/文本提词器/index.html new file mode 100644 index 0000000..dfc9383 --- /dev/null +++ b/文本提词器/index.html @@ -0,0 +1,130 @@ + + + + + + 网页版提词器 + + + +
+ +
+
+

文本输入

+ + +
+ +
+

滚动控制

+
+ + + 50 +
+
+ + + +
+
+ +
+

显示设置

+
+ + + 32px +
+
+ + + 1.8 +
+
+ + +
+
+ + +
+
+ + + 镜像 +
+
+ +
+
+

高级功能

+ +
+ +
+ +
+ + +
+ +
+

进度显示

+
+
+
+
+ 1 / 1 +
+
+ + + +
+
+ + + +
+ + +
+
+
+

请输入您的提词内容,然后点击"格式化文本"按钮开始使用...

+
+
+
+ + +
+
+ + + + \ No newline at end of file diff --git a/文本提词器/script.js b/文本提词器/script.js new file mode 100644 index 0000000..8ec6f42 --- /dev/null +++ b/文本提词器/script.js @@ -0,0 +1,772 @@ +class Teleprompter { + constructor() { + console.log('开始初始化提词器...'); + + this.isScrolling = false; + this.isPaused = false; + this.scrollInterval = null; + this.currentPosition = 0; + this.formattedText = []; + this.currentLine = 0; + this.totalLines = 0; + this.lineHeights = []; // 存储每行高度 + + this.initializeElements(); + this.bindEvents(); + this.loadSettings(); + this.initializeTextPosition(); + + console.log('提词器初始化完成'); + } + + initializeElements() { + console.log('正在初始化元素...'); + + // 文本输入 + this.textInput = document.getElementById('textInput'); + this.formatTextBtn = document.getElementById('formatText'); + + // 滚动控制 + this.scrollSpeed = document.getElementById('scrollSpeed'); + this.speedValue = document.getElementById('speedValue'); + this.startScrollBtn = document.getElementById('startScroll'); + this.pauseScrollBtn = document.getElementById('pauseScroll'); + this.resetScrollBtn = document.getElementById('resetScroll'); + + // 显示设置 + this.fontSize = document.getElementById('fontSize'); + this.fontSizeValue = document.getElementById('fontSizeValue'); + this.lineHeight = document.getElementById('lineHeight'); + this.lineHeightValue = document.getElementById('lineHeightValue'); + this.textColor = document.getElementById('textColor'); + this.bgColor = document.getElementById('bgColor'); + this.mirrorText = document.getElementById('mirrorText'); + + // 高级功能 + this.advancedToggle = document.getElementById('advancedToggle'); + this.advancedContent = document.getElementById('advancedContent'); + this.maxWordsPerParagraph = document.getElementById('maxWordsPerParagraph'); + this.maxWordsValue = document.getElementById('maxWordsValue'); + this.forceParagraphBreak = document.getElementById('forceParagraphBreak'); + this.formatTextBtn = document.getElementById('formatTextBtn'); + this.undoFormatBtn = document.getElementById('undoFormatBtn'); + this.mainFormatBtn = document.getElementById('formatText'); // 主显示按钮 + + // 显示区域 + this.textDisplay = document.getElementById('textDisplay'); + this.teleprompterContent = document.getElementById('teleprompterContent'); + this.readingGuide = document.getElementById('readingGuide'); + + // 控制面板 + this.controlPanel = document.getElementById('controlPanel'); + this.togglePanelBtn = document.getElementById('togglePanel'); + this.toggleFullscreenBtn = document.getElementById('toggleFullscreen'); + this.showPanelBtn = document.getElementById('showPanelBtn'); + + // 进度显示 + this.progressFill = document.getElementById('progressFill'); + this.currentLineSpan = document.getElementById('currentLine'); + this.totalLinesSpan = document.getElementById('totalLines'); + this.jumpToLineInput = document.getElementById('jumpToLine'); + this.jumpButton = document.getElementById('jumpButton'); + + // 容器 + this.container = document.querySelector('.container'); + this.teleprompterContainer = document.getElementById('teleprompterContainer'); + + console.log('元素初始化完成'); + } + + bindEvents() { + // 文本格式化 + this.formatTextBtn.addEventListener('click', () => this.smartFormatText()); // 高级功能中的格式化按钮 + this.undoFormatBtn.addEventListener('click', () => this.undoFormat()); + this.mainFormatBtn.addEventListener('click', () => this.formatText()); // 主显示按钮(简单显示) + + // 滚动控制 + this.scrollSpeed.addEventListener('input', () => { + this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1); + this.saveSettings(); + }); + + this.startScrollBtn.addEventListener('click', () => this.startScrolling()); + this.pauseScrollBtn.addEventListener('click', () => this.pauseScrolling()); + this.resetScrollBtn.addEventListener('click', () => this.resetScrolling()); + + // 显示设置 + this.fontSize.addEventListener('input', () => this.updateFontSize()); + this.lineHeight.addEventListener('input', () => this.updateLineHeight()); + this.textColor.addEventListener('input', () => this.updateTextColor()); + this.bgColor.addEventListener('input', () => this.updateBgColor()); + this.mirrorText.addEventListener('change', () => this.updateMirrorText()); + + // 高级功能 + this.advancedToggle.addEventListener('click', () => this.toggleAdvancedMenu()); + this.maxWordsPerParagraph.addEventListener('input', () => this.updateMaxWordsPerParagraph()); + this.forceParagraphBreak.addEventListener('change', () => this.updateForceParagraphBreak()); + + // 全屏和面板控制 + this.toggleFullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); + this.togglePanelBtn.addEventListener('click', () => this.togglePanel()); + this.showPanelBtn.addEventListener('click', () => this.togglePanel()); + + // 快速定位 + this.jumpButton.addEventListener('click', () => this.jumpToLine()); + this.jumpToLineInput.addEventListener('keypress', (e) => { + if (e.key === 'Enter') this.jumpToLine(); + }); + + // 键盘快捷键 + document.addEventListener('keydown', (e) => this.handleKeyboard(e)); + + // 窗口大小变化 + window.addEventListener('resize', () => this.handleResize()); + + // 全屏状态变化 + document.addEventListener('fullscreenchange', () => this.handleFullscreenChange()); + } + + initializeTextPosition() { + // 确保文本显示区域有正确的初始位置 + setTimeout(() => { + if (this.formattedText.length === 0) { + // 如果没有格式化文本,显示默认提示并定位到中间位置 + const containerHeight = this.teleprompterContent.clientHeight; + const lineHeight = this.getCurrentLineHeight(); + const startPosition = (containerHeight / 2) - lineHeight; + this.textDisplay.style.transform = `translateY(${startPosition}px)`; + } + }, 100); + } + + // 显示文本功能(简单显示,不格式化) + formatText() { + const text = this.textInput.value.trim(); + if (!text) { + alert('请先输入文本内容!'); + return; + } + + console.log('开始显示文本,输入长度:', text.length); + + // 简单按换行符分段,不进行智能格式化 + const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); + console.log('简单分段结果:', lines); + + this.formattedText = lines; + this.totalLines = lines.length; + this.currentLine = 0; + this.currentPosition = 0; + this.lineHeights = []; // 重置行高数组 + + // 更新跳转输入框的最大值 + this.jumpToLineInput.max = this.totalLines; + this.jumpToLineInput.value = 1; + + // 显示文本 + this.displayFormattedText(); + this.updateProgress(); + + // 保存到本地存储 + localStorage.setItem('teleprompterText', text); + + alert('文本显示完成!共' + lines.length + '行'); + } + + // 智能格式化功能 + smartFormatText() { + const text = this.textInput.value.trim(); + if (!text) { + alert('请先输入文本内容!'); + return; + } + + console.log('开始智能格式化文本,输入长度:', text.length); + + // 进行智能格式化 + const sentences = this.splitByPunctuation(text); + console.log('智能格式化结果:', sentences); + + this.formattedText = sentences; + this.totalLines = sentences.length; + this.currentLine = 0; + this.currentPosition = 0; + this.lineHeights = []; // 重置行高数组 + + // 更新跳转输入框的最大值 + this.jumpToLineInput.max = this.totalLines; + this.jumpToLineInput.value = 1; + + // 显示格式化后的文本 + this.displayFormattedText(); + this.updateProgress(); + + // 保存到本地存储 + localStorage.setItem('teleprompterText', text); + + alert('智能格式化完成!共' + sentences.length + '行'); + } + + // 撤销格式化功能 + undoFormat() { + const text = this.textInput.value.trim(); + if (!text) { + alert('请先输入文本内容!'); + return; + } + + console.log('开始撤销格式化,恢复原始段落'); + + // 简单按换行符分段,恢复原始段落结构 + const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); + console.log('撤销格式化结果:', lines); + + this.formattedText = lines; + this.totalLines = lines.length; + this.currentLine = 0; + this.currentPosition = 0; + this.lineHeights = []; // 重置行高数组 + + // 更新跳转输入框的最大值 + this.jumpToLineInput.max = this.totalLines; + this.jumpToLineInput.value = 1; + + // 显示格式化后的文本 + this.displayFormattedText(); + this.updateProgress(); + + // 保存到本地存储 + localStorage.setItem('teleprompterText', text); + + alert('格式化已撤销!共' + lines.length + '行'); + } + + splitByPunctuation(text) { + // 获取最大段落字数设置 + const maxWords = parseInt(this.maxWordsPerParagraph.value) || 50; + const forceBreak = this.forceParagraphBreak.checked; + + // 定义中文和英文的标点符号 + const punctuation = /[。!?;:.!?;:\n]/; + const sentences = []; + let current = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + current += char; + + // 如果遇到标点符号,或者文本长度超过限制 + if (punctuation.test(char) || current.length > maxWords) { + // 查找下一个非空白字符 + let j = i + 1; + while (j < text.length && /\s/.test(text[j])) { + j++; + } + + // 如果当前句子长度合理,或者已经累积了较多字符 + if (current.trim().length > 10 || sentences.length === 0) { + sentences.push(current.trim()); + current = ''; + i = j - 1; // 跳过空白字符 + } + } + } + + // 处理剩余的文本 + if (current.trim()) { + // 如果剩余文本较长且启用了强制分段,进一步分割 + if (forceBreak && current.length > maxWords * 1.5) { + const remainingSentences = this.forceSplitLongText(current, maxWords); + sentences.push(...remainingSentences); + } else { + sentences.push(current.trim()); + } + } + + return sentences; + } + + forceSplitLongText(text, maxWords) { + const sentences = []; + let current = ''; + + for (let i = 0; i < text.length; i++) { + const char = text[i]; + current += char; + + // 强制在最大字数处分割,优先在标点符号处 + if (current.length >= maxWords) { + // 向前查找最近的标点符号 + let breakPoint = current.length; + for (let j = current.length - 1; j >= Math.max(0, current.length - 20); j--) { + if (/[,。!?;:,\.!?;:]/.test(current[j])) { + breakPoint = j + 1; + break; + } + } + + const segment = current.substring(0, breakPoint).trim(); + if (segment.length > 10) { + sentences.push(segment); + } + + current = current.substring(breakPoint); + } + } + + if (current.trim()) { + sentences.push(current.trim()); + } + + return sentences; + } + + displayFormattedText() { + let html = ''; + this.lineHeights = []; // 重置行高数组 + + this.formattedText.forEach((sentence, index) => { + html += `

${sentence}

`; + }); + + if (html === '') { + html = '

请输入您的提词内容,然后点击"格式化文本"按钮开始使用...

'; + } + + this.textDisplay.innerHTML = html; + this.totalLinesSpan.textContent = this.totalLines; + + // 应用当前的行间距设置 + const currentLineHeight = this.lineHeight.value; + const paragraphs = this.textDisplay.querySelectorAll('p'); + paragraphs.forEach(p => { + p.style.lineHeight = currentLineHeight; + }); + + // 计算每行的高度并存储 + paragraphs.forEach((p, index) => { + const height = p.offsetHeight + parseFloat(getComputedStyle(p).marginBottom); + this.lineHeights[index] = height; + }); + + // 计算初始位置,将第一行文本对齐到中间位置 + const containerHeight = this.teleprompterContent.clientHeight; + const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight(); + const totalContentHeight = this.lineHeights.reduce((sum, height) => sum + height, 0); + + // 如果内容高度小于容器高度,将第一行放在中间位置 + // 如果内容高度大于容器高度,确保第一行不会被遮挡 + let startPosition = (containerHeight / 2) - firstLineHeight; + + // 确保起始位置不会导致文本显示在容器底部之外 + if (startPosition < 0) { + startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距 + } + + // 重置滚动位置 + this.currentPosition = 0; + const isMirror = this.mirrorText.checked; + const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; + this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`; + } + + // 滚动功能 + startScrolling() { + if (this.formattedText.length === 0) { + alert('请先格式化文本!'); + return; + } + + if (this.isPaused) { + this.resumeScrolling(); + return; + } + + this.isScrolling = true; + this.isPaused = false; + this.startScrollBtn.textContent = '滚动中...'; + this.startScrollBtn.disabled = true; + this.readingGuide.classList.add('visible'); + + const speed = parseFloat(this.scrollSpeed.value); + const interval = Math.max(20, 210 - speed); // 扩大速度转换范围,最低20ms,最高190ms + + this.scrollInterval = setInterval(() => { + this.scrollStep(); + }, interval); + } + + scrollStep() { + if (this.formattedText.length === 0) { + this.stopScrolling(); + alert('请先格式化文本!'); + return; + } + + const lineHeight = this.getCurrentLineHeight(); + this.currentPosition += 1; + + // 更新当前行 + let accumulatedHeight = 0; + let newLine = 0; + for (let i = 0; i < this.lineHeights.length; i++) { + accumulatedHeight += this.lineHeights[i]; + if (accumulatedHeight > this.currentPosition) { + newLine = i; + break; + } + newLine = i; + } + + if (newLine !== this.currentLine && newLine < this.totalLines) { + this.currentLine = newLine; + this.updateProgress(); + } + + // 应用滚动 + const isMirror = this.mirrorText.checked; + const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; + this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`; + + // 检查是否滚动到底部 + const containerHeight = this.teleprompterContent.clientHeight; + const contentHeight = this.textDisplay.scrollHeight; + + if (this.currentPosition + containerHeight >= contentHeight) { + this.stopScrolling(); + alert('已滚动到文本末尾!'); + } + } + + pauseScrolling() { + if (this.isScrolling && !this.isPaused) { + this.isPaused = true; + clearInterval(this.scrollInterval); + this.pauseScrollBtn.textContent = '继续'; + this.startScrollBtn.textContent = '继续'; + this.startScrollBtn.disabled = false; + } + } + + resumeScrolling() { + if (this.isPaused) { + this.isPaused = false; + this.startScrolling(); + this.pauseScrollBtn.textContent = '暂停'; + } + } + + resetScrolling() { + this.stopScrolling(); + this.currentPosition = 0; + this.currentLine = 0; + + // 重置到初始中间位置 + const containerHeight = this.teleprompterContent.clientHeight; + const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight(); + let startPosition = (containerHeight / 2) - firstLineHeight; + + // 确保起始位置不会导致文本显示在容器底部之外 + if (startPosition < 0) { + startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距 + } + + const isMirror = this.mirrorText.checked; + const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; + this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`; + + this.updateProgress(); + this.readingGuide.classList.remove('visible'); + } + + stopScrolling() { + this.isScrolling = false; + this.isPaused = false; + clearInterval(this.scrollInterval); + this.startScrollBtn.textContent = '开始'; + this.startScrollBtn.disabled = false; + this.pauseScrollBtn.textContent = '暂停'; + } + + // 显示设置 + updateFontSize() { + const size = this.fontSize.value; + this.fontSizeValue.textContent = size + 'px'; + this.textDisplay.style.fontSize = size + 'px'; + + // 重新计算行高 + if (this.formattedText.length > 0) { + setTimeout(() => this.displayFormattedText(), 50); + } + this.saveSettings(); + } + + updateLineHeight() { + const height = this.lineHeight.value; + this.lineHeightValue.textContent = height; + + // 应用到所有段落,让每一行都有这个行间距 + const paragraphs = this.textDisplay.querySelectorAll('p'); + paragraphs.forEach(p => { + p.style.lineHeight = height; + }); + + // 重新计算行高 + if (this.formattedText.length > 0) { + setTimeout(() => this.displayFormattedText(), 50); + } + this.saveSettings(); + } + + updateTextColor() { + const color = this.textColor.value; + this.textDisplay.style.color = color; + this.saveSettings(); + } + + updateBgColor() { + const color = this.bgColor.value; + this.teleprompterContainer.style.backgroundColor = color; + this.saveSettings(); + } + + updateMirrorText() { + const text = this.textInput.value.trim(); + if (text === '') { + this.textDisplay.innerHTML = ''; + this.formattedText = []; + return; + } + + // 简单按换行符分段,保持原始段落结构 + const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); + this.formattedText = lines; + this.totalLines = lines.length; + const formattedText = lines.map(line => `

${line}

`).join(''); + + this.textDisplay.innerHTML = formattedText; + + // 应用当前设置 + this.updateFontSize(); + this.updateLineHeight(); + this.updateTextColor(); + this.updateBgColor(); + + // 更新镜像状态 + const isMirror = this.mirrorText.checked; + if (isMirror) { + this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(-1)`; + } else { + this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(1)`; + } + + this.saveSettings(); + } + + // 高级功能 + toggleAdvancedMenu() { + const isVisible = this.advancedContent.style.display === 'block'; + this.advancedContent.style.display = isVisible ? 'none' : 'block'; + this.advancedToggle.style.transform = isVisible ? 'rotate(0deg)' : 'rotate(180deg)'; + } + + updateMaxWordsPerParagraph() { + const value = this.maxWordsPerParagraph.value; + this.maxWordsValue.textContent = value; + this.saveSettings(); + } + + updateForceParagraphBreak() { + this.saveSettings(); + } + + + + // 全屏功能 + toggleFullscreen() { + if (!document.fullscreenElement) { + this.container.requestFullscreen().then(() => { + this.container.classList.add('fullscreen'); + this.toggleFullscreenBtn.textContent = '退出全屏'; + }).catch(err => { + alert(`无法进入全屏模式: ${err.message}`); + }); + } else { + document.exitFullscreen().then(() => { + this.container.classList.remove('fullscreen'); + this.toggleFullscreenBtn.textContent = '全屏显示'; + }); + } + } + + togglePanel() { + this.controlPanel.classList.toggle('hidden'); + const isHidden = this.controlPanel.classList.contains('hidden'); + this.togglePanelBtn.textContent = isHidden ? '显示控制面板' : '隐藏控制面板'; + this.showPanelBtn.style.display = isHidden ? 'block' : 'none'; + + // 同时切换容器的panel-hidden类,用于扩展文本显示区域 + this.container.classList.toggle('panel-hidden', isHidden); + + // 切换body的背景样式,隐藏蓝色渐变背景 + document.body.classList.toggle('panel-hidden-bg', isHidden); + } + + // 键盘快捷键 + handleKeyboard(e) { + if (e.target.tagName === 'TEXTAREA') return; + + switch(e.key) { + case ' ': + e.preventDefault(); + if (this.isScrolling && !this.isPaused) { + this.pauseScrolling(); + } else { + this.startScrolling(); + } + break; + case 'r': + case 'R': + this.resetScrolling(); + break; + case 'f': + case 'F': + this.toggleFullscreen(); + break; + case 'Escape': + if (document.fullscreenElement) { + this.toggleFullscreen(); + } + break; + } + } + + // 快速定位功能 + jumpToLine() { + if (!this.formattedText || this.formattedText.length === 0) { + alert('请先格式化文本'); + return; + } + + const targetLine = parseInt(this.jumpToLineInput.value); + if (isNaN(targetLine) || targetLine < 1 || targetLine > this.formattedText.length) { + alert(`请输入有效的行号 (1-${this.formattedText.length})`); + return; + } + + this.stopScrolling(); + this.currentLine = targetLine - 1; // 转换为0基索引 + this.currentPosition = this.calculatePositionForLine(this.currentLine); + const isMirror = this.mirrorText.checked; + const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; + this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`; + this.updateProgress(); + } + + calculatePositionForLine(lineIndex) { + let position = 0; + for (let i = 0; i < lineIndex && i < this.lineHeights.length; i++) { + position += this.lineHeights[i] || this.getCurrentLineHeight(); + } + return position; + } + + // 进度更新 + updateProgress() { + const progress = this.totalLines > 0 ? (this.currentLine / this.totalLines) * 100 : 0; + this.progressFill.style.width = progress + '%'; + this.currentLineSpan.textContent = this.currentLine + 1; + } + + // 工具方法 + getCurrentLineHeight() { + const firstParagraph = this.textDisplay.querySelector('p'); + if (firstParagraph) { + return firstParagraph.offsetHeight + + parseFloat(getComputedStyle(firstParagraph).marginBottom); + } + return 50; // 默认值 + } + + handleResize() { + // 重新计算行高和位置 + if (this.formattedText.length > 0) { + this.displayFormattedText(); + } else { + // 如果没有格式化文本,重新初始化位置 + this.initializeTextPosition(); + } + } + + handleFullscreenChange() { + if (!document.fullscreenElement) { + this.container.classList.remove('fullscreen'); + this.toggleFullscreenBtn.textContent = '全屏显示'; + } + } + + // 设置保存和加载 + saveSettings() { + const settings = { + scrollSpeed: this.scrollSpeed.value, + fontSize: this.fontSize.value, + lineHeight: this.lineHeight.value, + textColor: this.textColor.value, + bgColor: this.bgColor.value, + mirrorText: this.mirrorText.checked, + maxWordsPerParagraph: this.maxWordsPerParagraph.value, + forceParagraphBreak: this.forceParagraphBreak.checked + }; + localStorage.setItem('teleprompterSettings', JSON.stringify(settings)); + } + + loadSettings() { + // 加载设置 + const settings = localStorage.getItem('teleprompterSettings'); + if (settings) { + const parsed = JSON.parse(settings); + this.scrollSpeed.value = parsed.scrollSpeed || 50; + this.fontSize.value = parsed.fontSize || 32; + this.lineHeight.value = parsed.lineHeight || 1.8; + this.textColor.value = parsed.textColor || '#ffffff'; + this.bgColor.value = parsed.bgColor || '#000000'; + this.mirrorText.checked = parsed.mirrorText || false; + this.maxWordsPerParagraph.value = parsed.maxWordsPerParagraph || 50; + this.forceParagraphBreak.checked = parsed.forceParagraphBreak !== undefined ? parsed.forceParagraphBreak : true; + + // 应用设置 + this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1); + this.updateFontSize(); + this.updateLineHeight(); + this.updateTextColor(); + this.updateBgColor(); + this.updateMirrorText(); + this.updateMaxWordsPerParagraph(); + this.updateForceParagraphBreak(); + } + + // 加载文本 + const savedText = localStorage.getItem('teleprompterText'); + if (savedText) { + this.textInput.value = savedText; + setTimeout(() => this.formatText(), 100); + } + } +} + +// 初始化应用 +document.addEventListener('DOMContentLoaded', () => { + new Teleprompter(); +}); + +// 添加一些提示信息 +window.addEventListener('load', () => { + setTimeout(() => { + console.log('提词器应用已加载完成!'); + console.log('快捷键说明:'); + console.log('- 空格键:开始/暂停滚动'); + console.log('- R键:重置滚动'); + console.log('- F键:切换全屏'); + console.log('- ESC键:退出全屏'); + }, 1000); +}); \ No newline at end of file diff --git a/文本提词器/styles.css b/文本提词器/styles.css new file mode 100644 index 0000000..39f1da9 --- /dev/null +++ b/文本提词器/styles.css @@ -0,0 +1,472 @@ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + height: 100vh; + overflow: hidden; +} + +.container { + display: flex; + height: 100vh; + position: relative; +} + +/* 控制面板样式 */ + .control-panel { + width: 320px; + background: rgba(255, 255, 255, 0.95); + backdrop-filter: blur(10px); + border-right: 1px solid rgba(255, 255, 255, 0.2); + padding: 20px; + overflow-y: auto; + transition: transform 0.3s ease; + box-shadow: 2px 0 20px rgba(0, 0, 0, 0.1); + position: relative; + z-index: 1000; + } + + .control-panel.hidden { + transform: translateX(-100%); + } + + .control-panel.hidden + .teleprompter-container { + margin-left: 0; + } + + .teleprompter-container { + transition: margin-left 0.3s ease; + } + +.panel-section { + margin-bottom: 25px; + padding: 15px; + background: rgba(255, 255, 255, 0.8); + border-radius: 10px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +/* 版权信息样式 */ +.copyright-section { + margin-top: 20px; + padding: 15px 0 5px 0; + background: rgba(255, 255, 255, 0.6); + border-radius: 8px; + border-top: 2px solid rgba(102, 126, 234, 0.3); +} + +.copyright-section p { + margin: 0; + text-align: center; + font-size: 12px; + color: #666; + font-weight: 500; + letter-spacing: 0.5px; +} + +.panel-section h3 { + color: #333; + margin-bottom: 15px; + font-size: 16px; + font-weight: 600; + border-bottom: 2px solid #667eea; + padding-bottom: 5px; +} + +/* 文本输入区域 */ +#textInput { + width: 100%; + padding: 12px; + border: 2px solid #e1e5e9; + border-radius: 8px; + font-size: 14px; + resize: vertical; + min-height: 120px; + font-family: inherit; + transition: border-color 0.3s ease; +} + +#textInput:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +/* 控制组 */ +.control-group { + margin-bottom: 15px; + display: flex; + align-items: center; + gap: 8px; + flex-wrap: nowrap; /* 防止换行 */ +} + +.control-group label { + font-weight: 500; + color: #555; + min-width: 60px; + font-size: 13px; + flex-shrink: 0; /* 防止标签收缩 */ +} + +.control-group input[type="range"] { + flex: 1; + min-width: 0; /* 允许滑块收缩 */ + height: 6px; + border-radius: 3px; + background: #ddd; + outline: none; + -webkit-appearance: none; +} + +.control-group input[type="range"]::-webkit-slider-thumb { + -webkit-appearance: none; + appearance: none; + width: 18px; + height: 18px; + border-radius: 50%; + background: #667eea; + cursor: pointer; + transition: background 0.3s ease; +} + +.control-group input[type="range"]::-webkit-slider-thumb:hover { + background: #5a6fd8; +} + +.control-group input[type="color"] { + width: 50px; + height: 30px; + border: none; + border-radius: 5px; + cursor: pointer; +} + +.control-group input[type="checkbox"] { + width: 18px; + height: 18px; + cursor: pointer; + accent-color: #667eea; +} + +.control-group span { + font-weight: 600; + color: #667eea; + min-width: 35px; + max-width: 40px; /* 限制最大宽度 */ + text-align: center; + font-size: 12px; + flex-shrink: 0; /* 防止数字显示收缩 */ +} + +/* 按钮样式 */ +button { + padding: 10px 16px; + border: none; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.3s ease; + margin: 5px; +} + +#formatText { + background: #28a745; + color: white; + width: 100%; + margin-top: 10px; +} + +#formatText:hover { + background: #218838; + transform: translateY(-1px); +} + +.control-buttons { + display: flex; + gap: 10px; + justify-content: center; +} + +#startScroll { + background: #007bff; + color: white; +} + +#startScroll:hover { + background: #0056b3; +} + +#pauseScroll { + background: #ffc107; + color: #212529; +} + +#pauseScroll:hover { + background: #e0a800; +} + +#resetScroll { + background: #dc3545; + color: white; +} + +#resetScroll:hover { + background: #c82333; +} + +#toggleFullscreen { + background: #6f42c1; + color: white; + width: 100%; + margin-bottom: 10px; +} + +#toggleFullscreen:hover { + background: #5a32a3; +} + +#togglePanel { + background: #17a2b8; + color: white; + width: 100%; +} + +#togglePanel:hover { + background: #138496; +} + +/* 进度条 */ +.progress-bar { + width: 100%; + height: 8px; + background: #e9ecef; + border-radius: 4px; + overflow: hidden; + margin-bottom: 10px; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #667eea, #764ba2); + width: 0%; + transition: width 0.3s ease; +} + +.progress-text { + text-align: center; + font-weight: 600; + color: #667eea; + font-size: 14px; +} + +/* 提词器显示区域 */ +.teleprompter-container { + flex: 1; + background: #000; + position: relative; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + transition: margin-left 0.3s ease; + min-height: 0; /* 修复flex项目的高度问题 */ +} + +/* 悬浮恢复按钮 */ +.show-panel-btn { + position: absolute; + top: 20px; + left: 20px; + background: rgba(23, 162, 184, 0.9); + color: white; + border: none; + padding: 10px 15px; + border-radius: 8px; + cursor: pointer; + font-size: 14px; + z-index: 1001; + transition: all 0.3s ease; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); +} + +.show-panel-btn:hover { + background: rgba(23, 162, 184, 1); + transform: translateY(-2px); + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); +} + +.teleprompter-content { + width: 98%; + max-width: 1400px; + height: 85vh; + position: relative; + overflow: hidden; + transition: width 0.3s ease, max-width 0.3s ease; /* 添加过渡效果 */ +} + +/* 控制面板隐藏时扩展文本内容区域 */ +.container.panel-hidden .teleprompter-content { + width: 100%; /* 占满整个可用宽度 */ + max-width: none; /* 移除最大宽度限制 */ + height: 100vh; /* 占满整个视口高度 */ + margin: 0; /* 移除边距 */ + padding: 0 40px; /* 添加一些内边距,让文本不会太靠边 */ +} + +.text-display { + padding: 50px 20px; + transition: transform 0.1s linear; + will-change: transform; + color: #ffffff; /* 确保文本颜色为白色 */ + font-size: 32px; /* 默认字体大小 */ + line-height: 1.8; /* 默认行高 */ + font-weight: 500; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); +} + +/* 控制面板隐藏时调整文本显示的内边距 */ +.container.panel-hidden .text-display { + padding: 50px 60px; /* 增加左右内边距,充分利用屏幕宽度 */ +} + +/* 全屏模式下的文本显示内边距 */ +.container.fullscreen .text-display { + padding: 50px 60px; /* 增加左右内边距,充分利用屏幕宽度 */ +} + +.text-display p { + margin-bottom: 0.5em; /* 减少段间距 */ + line-height: 1.8; /* 应用到每一行 */ + text-align: center; + font-weight: 400; + letter-spacing: 0.5px; + transition: all 0.3s ease; +} + +/* 阅读指导线 */ + .reading-guide { + position: absolute; + top: 50%; + left: 0; + right: 0; + height: 3px; + background: rgba(255, 255, 255, 0.8); + transform: translateY(-50%); + pointer-events: none; + opacity: 0; + transition: opacity 0.3s ease; + display: none; /* 隐藏阅读指导线 */ + } + +.reading-guide::before { + content: ''; + position: absolute; + top: -10px; + left: 0; + right: 0; + height: 20px; + background: linear-gradient( + 90deg, + transparent 0%, + rgba(255, 255, 255, 0.3) 20%, + rgba(255, 255, 255, 0.8) 50%, + rgba(255, 255, 255, 0.3) 80%, + transparent 100% + ); +} + +.reading-guide.visible { + opacity: 1; +} + +/* 全屏模式 */ +.container.fullscreen .control-panel { + display: none; +} + +.container.fullscreen .teleprompter-container { + width: 100vw; + height: 100vh; +} + +.container.fullscreen .teleprompter-content { + width: 100%; + max-width: none; + height: 100vh; + padding: 0 40px; /* 添加一些内边距,让文本不会太靠边 */ +} + +/* 控制面板隐藏时的全屏扩展 */ +.container.panel-hidden .teleprompter-container { + margin-left: 0; + width: 100vw; /* 使用视口宽度,占满整个屏幕 */ + border-radius: 0; + position: absolute; /* 使用绝对定位占满空间 */ + left: 0; + top: 0; + height: 100vh; /* 占满整个视口高度 */ +} + +.container.panel-hidden { + padding: 0; + border-radius: 0; + overflow: hidden; /* 防止出现滚动条 */ +} + +/* 当控制面板隐藏时,移除body的渐变背景 */ +body.panel-hidden-bg { + background: #000000; /* 纯黑色背景 */ +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .control-panel { + width: 280px; + padding: 15px; + } + + .teleprompter-content { + width: 98%; + max-width: none; + } + + .text-display { + padding: 30px 15px; + } +} + +@media (min-width: 1200px) { + .teleprompter-content { + width: 95%; + max-width: 1600px; + } +} + +/* 动画效果 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.panel-section { + animation: fadeIn 0.5s ease forwards; +} + +.panel-section:nth-child(1) { animation-delay: 0.1s; } +.panel-section:nth-child(2) { animation-delay: 0.2s; } +.panel-section:nth-child(3) { animation-delay: 0.3s; } +.panel-section:nth-child(4) { animation-delay: 0.4s; } +.panel-section:nth-child(5) { animation-delay: 0.5s; } \ No newline at end of file From ae2d5e35113f4591dee7fcffd88d7dc3671da4e0 Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Wed, 24 Sep 2025 19:20:15 +0800 Subject: [PATCH 3/6] Update README.md --- README.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 817d68c..1570652 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,10 @@ -# ticiqi -两种提词器网页的源码,包含文本提词器和pdf提词器,支持直接在本地使用,但是可能需要联网。 +# 提词器源码 +两种提词器网页的源码,包含文本提词器和pdf提词器,支持直接在本地使用,但是可能需要联网。 + +# 使用方法 +1.直接下载源码,本地浏览器打开即可,需要联网,部分js使用的是网络链接。 +2.部署到自己的服务器,只要支持http访问即可。 +3.使用Cloudflare Pages部署即可。 + +# 版权说明 +本项目完全开源,可用于个人,商用请署名,二次开发请署名。 From 4966135ff5dc0613057e4f60bb5dc2abf73d7dac Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Sat, 27 Dec 2025 13:04:55 +0800 Subject: [PATCH 4/6] =?UTF-8?q?Delete=20=E6=96=87=E6=9C=AC=E6=8F=90?= =?UTF-8?q?=E8=AF=8D=E5=99=A8=20directory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 文本提词器/index.html | 130 ------- 文本提词器/script.js | 772 ------------------------------------------ 文本提词器/styles.css | 472 -------------------------- 3 files changed, 1374 deletions(-) delete mode 100644 文本提词器/index.html delete mode 100644 文本提词器/script.js delete mode 100644 文本提词器/styles.css diff --git a/文本提词器/index.html b/文本提词器/index.html deleted file mode 100644 index dfc9383..0000000 --- a/文本提词器/index.html +++ /dev/null @@ -1,130 +0,0 @@ - - - - - - 网页版提词器 - - - -
- -
-
-

文本输入

- - -
- -
-

滚动控制

-
- - - 50 -
-
- - - -
-
- -
-

显示设置

-
- - - 32px -
-
- - - 1.8 -
-
- - -
-
- - -
-
- - - 镜像 -
-
- -
-
-

高级功能

- -
- -
- -
- - -
- -
-

进度显示

-
-
-
-
- 1 / 1 -
-
- - - -
-
- - - -
- - -
-
-
-

请输入您的提词内容,然后点击"格式化文本"按钮开始使用...

-
-
-
- - -
-
- - - - \ No newline at end of file diff --git a/文本提词器/script.js b/文本提词器/script.js deleted file mode 100644 index 8ec6f42..0000000 --- a/文本提词器/script.js +++ /dev/null @@ -1,772 +0,0 @@ -class Teleprompter { - constructor() { - console.log('开始初始化提词器...'); - - this.isScrolling = false; - this.isPaused = false; - this.scrollInterval = null; - this.currentPosition = 0; - this.formattedText = []; - this.currentLine = 0; - this.totalLines = 0; - this.lineHeights = []; // 存储每行高度 - - this.initializeElements(); - this.bindEvents(); - this.loadSettings(); - this.initializeTextPosition(); - - console.log('提词器初始化完成'); - } - - initializeElements() { - console.log('正在初始化元素...'); - - // 文本输入 - this.textInput = document.getElementById('textInput'); - this.formatTextBtn = document.getElementById('formatText'); - - // 滚动控制 - this.scrollSpeed = document.getElementById('scrollSpeed'); - this.speedValue = document.getElementById('speedValue'); - this.startScrollBtn = document.getElementById('startScroll'); - this.pauseScrollBtn = document.getElementById('pauseScroll'); - this.resetScrollBtn = document.getElementById('resetScroll'); - - // 显示设置 - this.fontSize = document.getElementById('fontSize'); - this.fontSizeValue = document.getElementById('fontSizeValue'); - this.lineHeight = document.getElementById('lineHeight'); - this.lineHeightValue = document.getElementById('lineHeightValue'); - this.textColor = document.getElementById('textColor'); - this.bgColor = document.getElementById('bgColor'); - this.mirrorText = document.getElementById('mirrorText'); - - // 高级功能 - this.advancedToggle = document.getElementById('advancedToggle'); - this.advancedContent = document.getElementById('advancedContent'); - this.maxWordsPerParagraph = document.getElementById('maxWordsPerParagraph'); - this.maxWordsValue = document.getElementById('maxWordsValue'); - this.forceParagraphBreak = document.getElementById('forceParagraphBreak'); - this.formatTextBtn = document.getElementById('formatTextBtn'); - this.undoFormatBtn = document.getElementById('undoFormatBtn'); - this.mainFormatBtn = document.getElementById('formatText'); // 主显示按钮 - - // 显示区域 - this.textDisplay = document.getElementById('textDisplay'); - this.teleprompterContent = document.getElementById('teleprompterContent'); - this.readingGuide = document.getElementById('readingGuide'); - - // 控制面板 - this.controlPanel = document.getElementById('controlPanel'); - this.togglePanelBtn = document.getElementById('togglePanel'); - this.toggleFullscreenBtn = document.getElementById('toggleFullscreen'); - this.showPanelBtn = document.getElementById('showPanelBtn'); - - // 进度显示 - this.progressFill = document.getElementById('progressFill'); - this.currentLineSpan = document.getElementById('currentLine'); - this.totalLinesSpan = document.getElementById('totalLines'); - this.jumpToLineInput = document.getElementById('jumpToLine'); - this.jumpButton = document.getElementById('jumpButton'); - - // 容器 - this.container = document.querySelector('.container'); - this.teleprompterContainer = document.getElementById('teleprompterContainer'); - - console.log('元素初始化完成'); - } - - bindEvents() { - // 文本格式化 - this.formatTextBtn.addEventListener('click', () => this.smartFormatText()); // 高级功能中的格式化按钮 - this.undoFormatBtn.addEventListener('click', () => this.undoFormat()); - this.mainFormatBtn.addEventListener('click', () => this.formatText()); // 主显示按钮(简单显示) - - // 滚动控制 - this.scrollSpeed.addEventListener('input', () => { - this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1); - this.saveSettings(); - }); - - this.startScrollBtn.addEventListener('click', () => this.startScrolling()); - this.pauseScrollBtn.addEventListener('click', () => this.pauseScrolling()); - this.resetScrollBtn.addEventListener('click', () => this.resetScrolling()); - - // 显示设置 - this.fontSize.addEventListener('input', () => this.updateFontSize()); - this.lineHeight.addEventListener('input', () => this.updateLineHeight()); - this.textColor.addEventListener('input', () => this.updateTextColor()); - this.bgColor.addEventListener('input', () => this.updateBgColor()); - this.mirrorText.addEventListener('change', () => this.updateMirrorText()); - - // 高级功能 - this.advancedToggle.addEventListener('click', () => this.toggleAdvancedMenu()); - this.maxWordsPerParagraph.addEventListener('input', () => this.updateMaxWordsPerParagraph()); - this.forceParagraphBreak.addEventListener('change', () => this.updateForceParagraphBreak()); - - // 全屏和面板控制 - this.toggleFullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); - this.togglePanelBtn.addEventListener('click', () => this.togglePanel()); - this.showPanelBtn.addEventListener('click', () => this.togglePanel()); - - // 快速定位 - this.jumpButton.addEventListener('click', () => this.jumpToLine()); - this.jumpToLineInput.addEventListener('keypress', (e) => { - if (e.key === 'Enter') this.jumpToLine(); - }); - - // 键盘快捷键 - document.addEventListener('keydown', (e) => this.handleKeyboard(e)); - - // 窗口大小变化 - window.addEventListener('resize', () => this.handleResize()); - - // 全屏状态变化 - document.addEventListener('fullscreenchange', () => this.handleFullscreenChange()); - } - - initializeTextPosition() { - // 确保文本显示区域有正确的初始位置 - setTimeout(() => { - if (this.formattedText.length === 0) { - // 如果没有格式化文本,显示默认提示并定位到中间位置 - const containerHeight = this.teleprompterContent.clientHeight; - const lineHeight = this.getCurrentLineHeight(); - const startPosition = (containerHeight / 2) - lineHeight; - this.textDisplay.style.transform = `translateY(${startPosition}px)`; - } - }, 100); - } - - // 显示文本功能(简单显示,不格式化) - formatText() { - const text = this.textInput.value.trim(); - if (!text) { - alert('请先输入文本内容!'); - return; - } - - console.log('开始显示文本,输入长度:', text.length); - - // 简单按换行符分段,不进行智能格式化 - const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); - console.log('简单分段结果:', lines); - - this.formattedText = lines; - this.totalLines = lines.length; - this.currentLine = 0; - this.currentPosition = 0; - this.lineHeights = []; // 重置行高数组 - - // 更新跳转输入框的最大值 - this.jumpToLineInput.max = this.totalLines; - this.jumpToLineInput.value = 1; - - // 显示文本 - this.displayFormattedText(); - this.updateProgress(); - - // 保存到本地存储 - localStorage.setItem('teleprompterText', text); - - alert('文本显示完成!共' + lines.length + '行'); - } - - // 智能格式化功能 - smartFormatText() { - const text = this.textInput.value.trim(); - if (!text) { - alert('请先输入文本内容!'); - return; - } - - console.log('开始智能格式化文本,输入长度:', text.length); - - // 进行智能格式化 - const sentences = this.splitByPunctuation(text); - console.log('智能格式化结果:', sentences); - - this.formattedText = sentences; - this.totalLines = sentences.length; - this.currentLine = 0; - this.currentPosition = 0; - this.lineHeights = []; // 重置行高数组 - - // 更新跳转输入框的最大值 - this.jumpToLineInput.max = this.totalLines; - this.jumpToLineInput.value = 1; - - // 显示格式化后的文本 - this.displayFormattedText(); - this.updateProgress(); - - // 保存到本地存储 - localStorage.setItem('teleprompterText', text); - - alert('智能格式化完成!共' + sentences.length + '行'); - } - - // 撤销格式化功能 - undoFormat() { - const text = this.textInput.value.trim(); - if (!text) { - alert('请先输入文本内容!'); - return; - } - - console.log('开始撤销格式化,恢复原始段落'); - - // 简单按换行符分段,恢复原始段落结构 - const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); - console.log('撤销格式化结果:', lines); - - this.formattedText = lines; - this.totalLines = lines.length; - this.currentLine = 0; - this.currentPosition = 0; - this.lineHeights = []; // 重置行高数组 - - // 更新跳转输入框的最大值 - this.jumpToLineInput.max = this.totalLines; - this.jumpToLineInput.value = 1; - - // 显示格式化后的文本 - this.displayFormattedText(); - this.updateProgress(); - - // 保存到本地存储 - localStorage.setItem('teleprompterText', text); - - alert('格式化已撤销!共' + lines.length + '行'); - } - - splitByPunctuation(text) { - // 获取最大段落字数设置 - const maxWords = parseInt(this.maxWordsPerParagraph.value) || 50; - const forceBreak = this.forceParagraphBreak.checked; - - // 定义中文和英文的标点符号 - const punctuation = /[。!?;:.!?;:\n]/; - const sentences = []; - let current = ''; - - for (let i = 0; i < text.length; i++) { - const char = text[i]; - current += char; - - // 如果遇到标点符号,或者文本长度超过限制 - if (punctuation.test(char) || current.length > maxWords) { - // 查找下一个非空白字符 - let j = i + 1; - while (j < text.length && /\s/.test(text[j])) { - j++; - } - - // 如果当前句子长度合理,或者已经累积了较多字符 - if (current.trim().length > 10 || sentences.length === 0) { - sentences.push(current.trim()); - current = ''; - i = j - 1; // 跳过空白字符 - } - } - } - - // 处理剩余的文本 - if (current.trim()) { - // 如果剩余文本较长且启用了强制分段,进一步分割 - if (forceBreak && current.length > maxWords * 1.5) { - const remainingSentences = this.forceSplitLongText(current, maxWords); - sentences.push(...remainingSentences); - } else { - sentences.push(current.trim()); - } - } - - return sentences; - } - - forceSplitLongText(text, maxWords) { - const sentences = []; - let current = ''; - - for (let i = 0; i < text.length; i++) { - const char = text[i]; - current += char; - - // 强制在最大字数处分割,优先在标点符号处 - if (current.length >= maxWords) { - // 向前查找最近的标点符号 - let breakPoint = current.length; - for (let j = current.length - 1; j >= Math.max(0, current.length - 20); j--) { - if (/[,。!?;:,\.!?;:]/.test(current[j])) { - breakPoint = j + 1; - break; - } - } - - const segment = current.substring(0, breakPoint).trim(); - if (segment.length > 10) { - sentences.push(segment); - } - - current = current.substring(breakPoint); - } - } - - if (current.trim()) { - sentences.push(current.trim()); - } - - return sentences; - } - - displayFormattedText() { - let html = ''; - this.lineHeights = []; // 重置行高数组 - - this.formattedText.forEach((sentence, index) => { - html += `

${sentence}

`; - }); - - if (html === '') { - html = '

请输入您的提词内容,然后点击"格式化文本"按钮开始使用...

'; - } - - this.textDisplay.innerHTML = html; - this.totalLinesSpan.textContent = this.totalLines; - - // 应用当前的行间距设置 - const currentLineHeight = this.lineHeight.value; - const paragraphs = this.textDisplay.querySelectorAll('p'); - paragraphs.forEach(p => { - p.style.lineHeight = currentLineHeight; - }); - - // 计算每行的高度并存储 - paragraphs.forEach((p, index) => { - const height = p.offsetHeight + parseFloat(getComputedStyle(p).marginBottom); - this.lineHeights[index] = height; - }); - - // 计算初始位置,将第一行文本对齐到中间位置 - const containerHeight = this.teleprompterContent.clientHeight; - const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight(); - const totalContentHeight = this.lineHeights.reduce((sum, height) => sum + height, 0); - - // 如果内容高度小于容器高度,将第一行放在中间位置 - // 如果内容高度大于容器高度,确保第一行不会被遮挡 - let startPosition = (containerHeight / 2) - firstLineHeight; - - // 确保起始位置不会导致文本显示在容器底部之外 - if (startPosition < 0) { - startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距 - } - - // 重置滚动位置 - this.currentPosition = 0; - const isMirror = this.mirrorText.checked; - const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; - this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`; - } - - // 滚动功能 - startScrolling() { - if (this.formattedText.length === 0) { - alert('请先格式化文本!'); - return; - } - - if (this.isPaused) { - this.resumeScrolling(); - return; - } - - this.isScrolling = true; - this.isPaused = false; - this.startScrollBtn.textContent = '滚动中...'; - this.startScrollBtn.disabled = true; - this.readingGuide.classList.add('visible'); - - const speed = parseFloat(this.scrollSpeed.value); - const interval = Math.max(20, 210 - speed); // 扩大速度转换范围,最低20ms,最高190ms - - this.scrollInterval = setInterval(() => { - this.scrollStep(); - }, interval); - } - - scrollStep() { - if (this.formattedText.length === 0) { - this.stopScrolling(); - alert('请先格式化文本!'); - return; - } - - const lineHeight = this.getCurrentLineHeight(); - this.currentPosition += 1; - - // 更新当前行 - let accumulatedHeight = 0; - let newLine = 0; - for (let i = 0; i < this.lineHeights.length; i++) { - accumulatedHeight += this.lineHeights[i]; - if (accumulatedHeight > this.currentPosition) { - newLine = i; - break; - } - newLine = i; - } - - if (newLine !== this.currentLine && newLine < this.totalLines) { - this.currentLine = newLine; - this.updateProgress(); - } - - // 应用滚动 - const isMirror = this.mirrorText.checked; - const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; - this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`; - - // 检查是否滚动到底部 - const containerHeight = this.teleprompterContent.clientHeight; - const contentHeight = this.textDisplay.scrollHeight; - - if (this.currentPosition + containerHeight >= contentHeight) { - this.stopScrolling(); - alert('已滚动到文本末尾!'); - } - } - - pauseScrolling() { - if (this.isScrolling && !this.isPaused) { - this.isPaused = true; - clearInterval(this.scrollInterval); - this.pauseScrollBtn.textContent = '继续'; - this.startScrollBtn.textContent = '继续'; - this.startScrollBtn.disabled = false; - } - } - - resumeScrolling() { - if (this.isPaused) { - this.isPaused = false; - this.startScrolling(); - this.pauseScrollBtn.textContent = '暂停'; - } - } - - resetScrolling() { - this.stopScrolling(); - this.currentPosition = 0; - this.currentLine = 0; - - // 重置到初始中间位置 - const containerHeight = this.teleprompterContent.clientHeight; - const firstLineHeight = this.lineHeights[0] || this.getCurrentLineHeight(); - let startPosition = (containerHeight / 2) - firstLineHeight; - - // 确保起始位置不会导致文本显示在容器底部之外 - if (startPosition < 0) { - startPosition = Math.min(50, containerHeight / 4); // 给一个合理的顶部边距 - } - - const isMirror = this.mirrorText.checked; - const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; - this.textDisplay.style.transform = `translateY(${startPosition}px)${mirrorScale}`; - - this.updateProgress(); - this.readingGuide.classList.remove('visible'); - } - - stopScrolling() { - this.isScrolling = false; - this.isPaused = false; - clearInterval(this.scrollInterval); - this.startScrollBtn.textContent = '开始'; - this.startScrollBtn.disabled = false; - this.pauseScrollBtn.textContent = '暂停'; - } - - // 显示设置 - updateFontSize() { - const size = this.fontSize.value; - this.fontSizeValue.textContent = size + 'px'; - this.textDisplay.style.fontSize = size + 'px'; - - // 重新计算行高 - if (this.formattedText.length > 0) { - setTimeout(() => this.displayFormattedText(), 50); - } - this.saveSettings(); - } - - updateLineHeight() { - const height = this.lineHeight.value; - this.lineHeightValue.textContent = height; - - // 应用到所有段落,让每一行都有这个行间距 - const paragraphs = this.textDisplay.querySelectorAll('p'); - paragraphs.forEach(p => { - p.style.lineHeight = height; - }); - - // 重新计算行高 - if (this.formattedText.length > 0) { - setTimeout(() => this.displayFormattedText(), 50); - } - this.saveSettings(); - } - - updateTextColor() { - const color = this.textColor.value; - this.textDisplay.style.color = color; - this.saveSettings(); - } - - updateBgColor() { - const color = this.bgColor.value; - this.teleprompterContainer.style.backgroundColor = color; - this.saveSettings(); - } - - updateMirrorText() { - const text = this.textInput.value.trim(); - if (text === '') { - this.textDisplay.innerHTML = ''; - this.formattedText = []; - return; - } - - // 简单按换行符分段,保持原始段落结构 - const lines = text.split('\n').map(line => line.trim()).filter(line => line !== ''); - this.formattedText = lines; - this.totalLines = lines.length; - const formattedText = lines.map(line => `

${line}

`).join(''); - - this.textDisplay.innerHTML = formattedText; - - // 应用当前设置 - this.updateFontSize(); - this.updateLineHeight(); - this.updateTextColor(); - this.updateBgColor(); - - // 更新镜像状态 - const isMirror = this.mirrorText.checked; - if (isMirror) { - this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(-1)`; - } else { - this.textDisplay.style.transform = `translateY(-${this.currentPosition}px) scaleX(1)`; - } - - this.saveSettings(); - } - - // 高级功能 - toggleAdvancedMenu() { - const isVisible = this.advancedContent.style.display === 'block'; - this.advancedContent.style.display = isVisible ? 'none' : 'block'; - this.advancedToggle.style.transform = isVisible ? 'rotate(0deg)' : 'rotate(180deg)'; - } - - updateMaxWordsPerParagraph() { - const value = this.maxWordsPerParagraph.value; - this.maxWordsValue.textContent = value; - this.saveSettings(); - } - - updateForceParagraphBreak() { - this.saveSettings(); - } - - - - // 全屏功能 - toggleFullscreen() { - if (!document.fullscreenElement) { - this.container.requestFullscreen().then(() => { - this.container.classList.add('fullscreen'); - this.toggleFullscreenBtn.textContent = '退出全屏'; - }).catch(err => { - alert(`无法进入全屏模式: ${err.message}`); - }); - } else { - document.exitFullscreen().then(() => { - this.container.classList.remove('fullscreen'); - this.toggleFullscreenBtn.textContent = '全屏显示'; - }); - } - } - - togglePanel() { - this.controlPanel.classList.toggle('hidden'); - const isHidden = this.controlPanel.classList.contains('hidden'); - this.togglePanelBtn.textContent = isHidden ? '显示控制面板' : '隐藏控制面板'; - this.showPanelBtn.style.display = isHidden ? 'block' : 'none'; - - // 同时切换容器的panel-hidden类,用于扩展文本显示区域 - this.container.classList.toggle('panel-hidden', isHidden); - - // 切换body的背景样式,隐藏蓝色渐变背景 - document.body.classList.toggle('panel-hidden-bg', isHidden); - } - - // 键盘快捷键 - handleKeyboard(e) { - if (e.target.tagName === 'TEXTAREA') return; - - switch(e.key) { - case ' ': - e.preventDefault(); - if (this.isScrolling && !this.isPaused) { - this.pauseScrolling(); - } else { - this.startScrolling(); - } - break; - case 'r': - case 'R': - this.resetScrolling(); - break; - case 'f': - case 'F': - this.toggleFullscreen(); - break; - case 'Escape': - if (document.fullscreenElement) { - this.toggleFullscreen(); - } - break; - } - } - - // 快速定位功能 - jumpToLine() { - if (!this.formattedText || this.formattedText.length === 0) { - alert('请先格式化文本'); - return; - } - - const targetLine = parseInt(this.jumpToLineInput.value); - if (isNaN(targetLine) || targetLine < 1 || targetLine > this.formattedText.length) { - alert(`请输入有效的行号 (1-${this.formattedText.length})`); - return; - } - - this.stopScrolling(); - this.currentLine = targetLine - 1; // 转换为0基索引 - this.currentPosition = this.calculatePositionForLine(this.currentLine); - const isMirror = this.mirrorText.checked; - const mirrorScale = isMirror ? ' scaleX(-1)' : ' scaleX(1)'; - this.textDisplay.style.transform = `translateY(-${this.currentPosition}px)${mirrorScale}`; - this.updateProgress(); - } - - calculatePositionForLine(lineIndex) { - let position = 0; - for (let i = 0; i < lineIndex && i < this.lineHeights.length; i++) { - position += this.lineHeights[i] || this.getCurrentLineHeight(); - } - return position; - } - - // 进度更新 - updateProgress() { - const progress = this.totalLines > 0 ? (this.currentLine / this.totalLines) * 100 : 0; - this.progressFill.style.width = progress + '%'; - this.currentLineSpan.textContent = this.currentLine + 1; - } - - // 工具方法 - getCurrentLineHeight() { - const firstParagraph = this.textDisplay.querySelector('p'); - if (firstParagraph) { - return firstParagraph.offsetHeight + - parseFloat(getComputedStyle(firstParagraph).marginBottom); - } - return 50; // 默认值 - } - - handleResize() { - // 重新计算行高和位置 - if (this.formattedText.length > 0) { - this.displayFormattedText(); - } else { - // 如果没有格式化文本,重新初始化位置 - this.initializeTextPosition(); - } - } - - handleFullscreenChange() { - if (!document.fullscreenElement) { - this.container.classList.remove('fullscreen'); - this.toggleFullscreenBtn.textContent = '全屏显示'; - } - } - - // 设置保存和加载 - saveSettings() { - const settings = { - scrollSpeed: this.scrollSpeed.value, - fontSize: this.fontSize.value, - lineHeight: this.lineHeight.value, - textColor: this.textColor.value, - bgColor: this.bgColor.value, - mirrorText: this.mirrorText.checked, - maxWordsPerParagraph: this.maxWordsPerParagraph.value, - forceParagraphBreak: this.forceParagraphBreak.checked - }; - localStorage.setItem('teleprompterSettings', JSON.stringify(settings)); - } - - loadSettings() { - // 加载设置 - const settings = localStorage.getItem('teleprompterSettings'); - if (settings) { - const parsed = JSON.parse(settings); - this.scrollSpeed.value = parsed.scrollSpeed || 50; - this.fontSize.value = parsed.fontSize || 32; - this.lineHeight.value = parsed.lineHeight || 1.8; - this.textColor.value = parsed.textColor || '#ffffff'; - this.bgColor.value = parsed.bgColor || '#000000'; - this.mirrorText.checked = parsed.mirrorText || false; - this.maxWordsPerParagraph.value = parsed.maxWordsPerParagraph || 50; - this.forceParagraphBreak.checked = parsed.forceParagraphBreak !== undefined ? parsed.forceParagraphBreak : true; - - // 应用设置 - this.speedValue.textContent = parseFloat(this.scrollSpeed.value).toFixed(1); - this.updateFontSize(); - this.updateLineHeight(); - this.updateTextColor(); - this.updateBgColor(); - this.updateMirrorText(); - this.updateMaxWordsPerParagraph(); - this.updateForceParagraphBreak(); - } - - // 加载文本 - const savedText = localStorage.getItem('teleprompterText'); - if (savedText) { - this.textInput.value = savedText; - setTimeout(() => this.formatText(), 100); - } - } -} - -// 初始化应用 -document.addEventListener('DOMContentLoaded', () => { - new Teleprompter(); -}); - -// 添加一些提示信息 -window.addEventListener('load', () => { - setTimeout(() => { - console.log('提词器应用已加载完成!'); - console.log('快捷键说明:'); - console.log('- 空格键:开始/暂停滚动'); - console.log('- R键:重置滚动'); - console.log('- F键:切换全屏'); - console.log('- ESC键:退出全屏'); - }, 1000); -}); \ No newline at end of file diff --git a/文本提词器/styles.css b/文本提词器/styles.css deleted file mode 100644 index 39f1da9..0000000 --- a/文本提词器/styles.css +++ /dev/null @@ -1,472 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: 'Microsoft YaHei', 'PingFang SC', 'Helvetica Neue', Arial, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - height: 100vh; - overflow: hidden; -} - -.container { - display: flex; - height: 100vh; - position: relative; -} - -/* 控制面板样式 */ - .control-panel { - width: 320px; - background: rgba(255, 255, 255, 0.95); - backdrop-filter: blur(10px); - border-right: 1px solid rgba(255, 255, 255, 0.2); - padding: 20px; - overflow-y: auto; - transition: transform 0.3s ease; - box-shadow: 2px 0 20px rgba(0, 0, 0, 0.1); - position: relative; - z-index: 1000; - } - - .control-panel.hidden { - transform: translateX(-100%); - } - - .control-panel.hidden + .teleprompter-container { - margin-left: 0; - } - - .teleprompter-container { - transition: margin-left 0.3s ease; - } - -.panel-section { - margin-bottom: 25px; - padding: 15px; - background: rgba(255, 255, 255, 0.8); - border-radius: 10px; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); -} - -/* 版权信息样式 */ -.copyright-section { - margin-top: 20px; - padding: 15px 0 5px 0; - background: rgba(255, 255, 255, 0.6); - border-radius: 8px; - border-top: 2px solid rgba(102, 126, 234, 0.3); -} - -.copyright-section p { - margin: 0; - text-align: center; - font-size: 12px; - color: #666; - font-weight: 500; - letter-spacing: 0.5px; -} - -.panel-section h3 { - color: #333; - margin-bottom: 15px; - font-size: 16px; - font-weight: 600; - border-bottom: 2px solid #667eea; - padding-bottom: 5px; -} - -/* 文本输入区域 */ -#textInput { - width: 100%; - padding: 12px; - border: 2px solid #e1e5e9; - border-radius: 8px; - font-size: 14px; - resize: vertical; - min-height: 120px; - font-family: inherit; - transition: border-color 0.3s ease; -} - -#textInput:focus { - outline: none; - border-color: #667eea; - box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); -} - -/* 控制组 */ -.control-group { - margin-bottom: 15px; - display: flex; - align-items: center; - gap: 8px; - flex-wrap: nowrap; /* 防止换行 */ -} - -.control-group label { - font-weight: 500; - color: #555; - min-width: 60px; - font-size: 13px; - flex-shrink: 0; /* 防止标签收缩 */ -} - -.control-group input[type="range"] { - flex: 1; - min-width: 0; /* 允许滑块收缩 */ - height: 6px; - border-radius: 3px; - background: #ddd; - outline: none; - -webkit-appearance: none; -} - -.control-group input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: #667eea; - cursor: pointer; - transition: background 0.3s ease; -} - -.control-group input[type="range"]::-webkit-slider-thumb:hover { - background: #5a6fd8; -} - -.control-group input[type="color"] { - width: 50px; - height: 30px; - border: none; - border-radius: 5px; - cursor: pointer; -} - -.control-group input[type="checkbox"] { - width: 18px; - height: 18px; - cursor: pointer; - accent-color: #667eea; -} - -.control-group span { - font-weight: 600; - color: #667eea; - min-width: 35px; - max-width: 40px; /* 限制最大宽度 */ - text-align: center; - font-size: 12px; - flex-shrink: 0; /* 防止数字显示收缩 */ -} - -/* 按钮样式 */ -button { - padding: 10px 16px; - border: none; - border-radius: 8px; - font-size: 14px; - font-weight: 500; - cursor: pointer; - transition: all 0.3s ease; - margin: 5px; -} - -#formatText { - background: #28a745; - color: white; - width: 100%; - margin-top: 10px; -} - -#formatText:hover { - background: #218838; - transform: translateY(-1px); -} - -.control-buttons { - display: flex; - gap: 10px; - justify-content: center; -} - -#startScroll { - background: #007bff; - color: white; -} - -#startScroll:hover { - background: #0056b3; -} - -#pauseScroll { - background: #ffc107; - color: #212529; -} - -#pauseScroll:hover { - background: #e0a800; -} - -#resetScroll { - background: #dc3545; - color: white; -} - -#resetScroll:hover { - background: #c82333; -} - -#toggleFullscreen { - background: #6f42c1; - color: white; - width: 100%; - margin-bottom: 10px; -} - -#toggleFullscreen:hover { - background: #5a32a3; -} - -#togglePanel { - background: #17a2b8; - color: white; - width: 100%; -} - -#togglePanel:hover { - background: #138496; -} - -/* 进度条 */ -.progress-bar { - width: 100%; - height: 8px; - background: #e9ecef; - border-radius: 4px; - overflow: hidden; - margin-bottom: 10px; -} - -.progress-fill { - height: 100%; - background: linear-gradient(90deg, #667eea, #764ba2); - width: 0%; - transition: width 0.3s ease; -} - -.progress-text { - text-align: center; - font-weight: 600; - color: #667eea; - font-size: 14px; -} - -/* 提词器显示区域 */ -.teleprompter-container { - flex: 1; - background: #000; - position: relative; - overflow: hidden; - display: flex; - align-items: center; - justify-content: center; - transition: margin-left 0.3s ease; - min-height: 0; /* 修复flex项目的高度问题 */ -} - -/* 悬浮恢复按钮 */ -.show-panel-btn { - position: absolute; - top: 20px; - left: 20px; - background: rgba(23, 162, 184, 0.9); - color: white; - border: none; - padding: 10px 15px; - border-radius: 8px; - cursor: pointer; - font-size: 14px; - z-index: 1001; - transition: all 0.3s ease; - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3); -} - -.show-panel-btn:hover { - background: rgba(23, 162, 184, 1); - transform: translateY(-2px); - box-shadow: 0 4px 15px rgba(0, 0, 0, 0.4); -} - -.teleprompter-content { - width: 98%; - max-width: 1400px; - height: 85vh; - position: relative; - overflow: hidden; - transition: width 0.3s ease, max-width 0.3s ease; /* 添加过渡效果 */ -} - -/* 控制面板隐藏时扩展文本内容区域 */ -.container.panel-hidden .teleprompter-content { - width: 100%; /* 占满整个可用宽度 */ - max-width: none; /* 移除最大宽度限制 */ - height: 100vh; /* 占满整个视口高度 */ - margin: 0; /* 移除边距 */ - padding: 0 40px; /* 添加一些内边距,让文本不会太靠边 */ -} - -.text-display { - padding: 50px 20px; - transition: transform 0.1s linear; - will-change: transform; - color: #ffffff; /* 确保文本颜色为白色 */ - font-size: 32px; /* 默认字体大小 */ - line-height: 1.8; /* 默认行高 */ - font-weight: 500; - text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5); -} - -/* 控制面板隐藏时调整文本显示的内边距 */ -.container.panel-hidden .text-display { - padding: 50px 60px; /* 增加左右内边距,充分利用屏幕宽度 */ -} - -/* 全屏模式下的文本显示内边距 */ -.container.fullscreen .text-display { - padding: 50px 60px; /* 增加左右内边距,充分利用屏幕宽度 */ -} - -.text-display p { - margin-bottom: 0.5em; /* 减少段间距 */ - line-height: 1.8; /* 应用到每一行 */ - text-align: center; - font-weight: 400; - letter-spacing: 0.5px; - transition: all 0.3s ease; -} - -/* 阅读指导线 */ - .reading-guide { - position: absolute; - top: 50%; - left: 0; - right: 0; - height: 3px; - background: rgba(255, 255, 255, 0.8); - transform: translateY(-50%); - pointer-events: none; - opacity: 0; - transition: opacity 0.3s ease; - display: none; /* 隐藏阅读指导线 */ - } - -.reading-guide::before { - content: ''; - position: absolute; - top: -10px; - left: 0; - right: 0; - height: 20px; - background: linear-gradient( - 90deg, - transparent 0%, - rgba(255, 255, 255, 0.3) 20%, - rgba(255, 255, 255, 0.8) 50%, - rgba(255, 255, 255, 0.3) 80%, - transparent 100% - ); -} - -.reading-guide.visible { - opacity: 1; -} - -/* 全屏模式 */ -.container.fullscreen .control-panel { - display: none; -} - -.container.fullscreen .teleprompter-container { - width: 100vw; - height: 100vh; -} - -.container.fullscreen .teleprompter-content { - width: 100%; - max-width: none; - height: 100vh; - padding: 0 40px; /* 添加一些内边距,让文本不会太靠边 */ -} - -/* 控制面板隐藏时的全屏扩展 */ -.container.panel-hidden .teleprompter-container { - margin-left: 0; - width: 100vw; /* 使用视口宽度,占满整个屏幕 */ - border-radius: 0; - position: absolute; /* 使用绝对定位占满空间 */ - left: 0; - top: 0; - height: 100vh; /* 占满整个视口高度 */ -} - -.container.panel-hidden { - padding: 0; - border-radius: 0; - overflow: hidden; /* 防止出现滚动条 */ -} - -/* 当控制面板隐藏时,移除body的渐变背景 */ -body.panel-hidden-bg { - background: #000000; /* 纯黑色背景 */ -} - -/* 响应式设计 */ -@media (max-width: 768px) { - .control-panel { - width: 280px; - padding: 15px; - } - - .teleprompter-content { - width: 98%; - max-width: none; - } - - .text-display { - padding: 30px 15px; - } -} - -@media (min-width: 1200px) { - .teleprompter-content { - width: 95%; - max-width: 1600px; - } -} - -/* 动画效果 */ -@keyframes fadeIn { - from { - opacity: 0; - transform: translateY(20px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -.panel-section { - animation: fadeIn 0.5s ease forwards; -} - -.panel-section:nth-child(1) { animation-delay: 0.1s; } -.panel-section:nth-child(2) { animation-delay: 0.2s; } -.panel-section:nth-child(3) { animation-delay: 0.3s; } -.panel-section:nth-child(4) { animation-delay: 0.4s; } -.panel-section:nth-child(5) { animation-delay: 0.5s; } \ No newline at end of file From b806e5e30c2be2964d685dbe022edab8890b87e6 Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Sat, 27 Dec 2025 13:09:21 +0800 Subject: [PATCH 5/6] =?UTF-8?q?Delete=20pdf=E6=8F=90=E8=AF=8D=E5=99=A8=20d?= =?UTF-8?q?irectory?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pdf提词器/index.html | 88 ---------- pdf提词器/script.js | 408 ------------------------------------------- pdf提词器/styles.css | 407 ------------------------------------------ 3 files changed, 903 deletions(-) delete mode 100644 pdf提词器/index.html delete mode 100644 pdf提词器/script.js delete mode 100644 pdf提词器/styles.css diff --git a/pdf提词器/index.html b/pdf提词器/index.html deleted file mode 100644 index ebbcc69..0000000 --- a/pdf提词器/index.html +++ /dev/null @@ -1,88 +0,0 @@ - - - - - - PDF提词器 - - - -
-
-

PDF提词器

-

上传PDF文件,自动生成镜像提词效果

-
- -
-
- - - - - -

点击或拖拽PDF文件到此处

- -
-
- - - - - - - - -
- - - - - \ No newline at end of file diff --git a/pdf提词器/script.js b/pdf提词器/script.js deleted file mode 100644 index c81e4c9..0000000 --- a/pdf提词器/script.js +++ /dev/null @@ -1,408 +0,0 @@ -class PDFTeleprompter { - constructor() { - this.pdfDoc = null; - this.pages = []; - this.isPlaying = false; - this.animationId = null; - this.scrollPosition = 0; - this.scrollSpeed = 2; - - this.initializeElements(); - this.bindEvents(); - } - - initializeElements() { - this.uploadArea = document.getElementById('uploadArea'); - this.fileInput = document.getElementById('fileInput'); - this.controls = document.getElementById('controls'); - this.displayArea = document.getElementById('displayArea'); - this.teleprompterContent = document.getElementById('teleprompterContent'); - this.loading = document.getElementById('loading'); - this.speedSlider = document.getElementById('speedSlider'); - this.speedValue = document.getElementById('speedValue'); - this.playBtn = document.getElementById('playBtn'); - this.pauseBtn = document.getElementById('pauseBtn'); - this.resetBtn = document.getElementById('resetBtn'); - this.fullscreenBtn = document.getElementById('fullscreenBtn'); - - // 悬浮控制面板元素 - this.floatingControls = document.getElementById('floatingControls'); - this.floatPlayBtn = document.getElementById('floatPlayBtn'); - this.floatResetBtn = document.getElementById('floatResetBtn'); - this.floatExitBtn = document.getElementById('floatExitBtn'); - this.floatSpeedSlider = document.getElementById('floatSpeedSlider'); - this.floatSpeedValue = document.getElementById('floatSpeedValue'); - - // 滚动容器 - this.teleprompterContainer = document.querySelector('.teleprompter-container'); - } - - bindEvents() { - this.uploadArea.addEventListener('click', () => this.fileInput.click()); - this.fileInput.addEventListener('change', (e) => this.handleFileUpload(e)); - this.uploadArea.addEventListener('dragover', (e) => this.handleDragOver(e)); - this.uploadArea.addEventListener('drop', (e) => this.handleDrop(e)); - - this.speedSlider.addEventListener('input', (e) => { - this.scrollSpeed = parseFloat(e.target.value); - this.speedValue.textContent = this.scrollSpeed; - if (this.floatSpeedSlider) { - this.floatSpeedSlider.value = this.scrollSpeed; - this.floatSpeedValue.textContent = this.scrollSpeed; - } - }); - - this.playBtn.addEventListener('click', () => this.play()); - this.pauseBtn.addEventListener('click', () => this.pause()); - this.resetBtn.addEventListener('click', () => this.reset()); - this.fullscreenBtn.addEventListener('click', () => this.toggleFullscreen()); - - // 悬浮控制面板事件 - if (this.floatPlayBtn) { - this.floatPlayBtn.addEventListener('click', () => this.togglePlayPause()); - this.floatResetBtn.addEventListener('click', () => this.reset()); - this.floatExitBtn.addEventListener('click', () => this.exitFullscreen()); - - this.floatSpeedSlider.addEventListener('input', (e) => { - this.scrollSpeed = parseFloat(e.target.value); - this.floatSpeedValue.textContent = this.scrollSpeed; - this.speedSlider.value = this.scrollSpeed; - this.speedValue.textContent = this.scrollSpeed; - }); - } - - // 全屏状态变化监听 - ['fullscreenchange', 'webkitfullscreenchange', 'mozfullscreenchange', 'MSFullscreenChange'].forEach(event => { - document.addEventListener(event, () => this.handleFullscreenChange()); - }); - - // 手动滚动监听 - this.teleprompterContainer.addEventListener('scroll', () => { - if (!this.isPlaying) { - this.scrollPosition = this.teleprompterContainer.scrollTop; - } - }); - - // 防止滚动时自动播放 - this.teleprompterContainer.addEventListener('mousedown', () => { - if (this.isPlaying) { - this.pause(); - } - }); - - // 键盘快捷键 - document.addEventListener('keydown', (e) => { - if (e.key === ' ' || e.key === 'Spacebar') { - e.preventDefault(); - this.togglePlayPause(); - } else if (e.key === 'f' || e.key === 'F') { - e.preventDefault(); - this.toggleFullscreen(); - } else if (e.key === 'Escape') { - if (document.fullscreenElement) { - this.exitFullscreen(); - } - } - }); - - // 触摸手势支持 - let touchStartY = 0; - this.teleprompterContainer.addEventListener('touchstart', (e) => { - touchStartY = e.touches[0].clientY; - if (this.isPlaying) { - this.pause(); - } - }); - - this.teleprompterContainer.addEventListener('touchmove', (e) => { - e.preventDefault(); - const touchY = e.touches[0].clientY; - const deltaY = touchStartY - touchY; - this.teleprompterContainer.scrollTop += deltaY; - touchStartY = touchY; - this.scrollPosition = this.teleprompterContainer.scrollTop; - }); - } - - async handleFileUpload(event) { - const file = event.target.files[0]; - if (file && file.type === 'application/pdf') { - await this.processPDF(file); - } - } - - async processPDF(file) { - this.showLoading(true); - - try { - const arrayBuffer = await file.arrayBuffer(); - this.pdfDoc = await pdfjsLib.getDocument({ data: arrayBuffer }).promise; - - await this.renderAllPages(); - this.showControls(); - - } catch (error) { - console.error('Error processing PDF:', error); - alert('处理PDF文件时出错,请重试'); - } finally { - this.showLoading(false); - } - } - - async renderAllPages() { - this.pages = []; - this.teleprompterContent.innerHTML = ''; - - const totalPages = this.pdfDoc.numPages; - - for (let pageNum = 1; pageNum <= totalPages; pageNum++) { - const page = await this.pdfDoc.getPage(pageNum); - const canvas = await this.renderPageToCanvas(page); - - const img = document.createElement('img'); - img.src = canvas.toDataURL('image/png'); - img.style.width = '100%'; - img.style.height = 'auto'; - img.style.marginBottom = '20px'; - - this.teleprompterContent.appendChild(img); - this.pages.push(img); - } - } - - async renderPageToCanvas(page) { - const viewport = page.getViewport({ scale: 1.5 }); - const canvas = document.createElement('canvas'); - const context = canvas.getContext('2d'); - - canvas.width = viewport.width; - canvas.height = viewport.height; - - const renderContext = { - canvasContext: context, - viewport: viewport - }; - - await page.render(renderContext).promise; - return canvas; - } - - showLoading(show) { - this.loading.style.display = show ? 'block' : 'none'; - } - - showControls() { - this.uploadArea.parentElement.style.display = 'none'; - this.controls.style.display = 'flex'; - this.displayArea.style.display = 'block'; - } - - play() { - if (!this.isPlaying) { - this.isPlaying = true; - this.playBtn.disabled = true; - this.pauseBtn.disabled = false; - this.animateScroll(); - } - } - - pause() { - this.isPlaying = false; - this.playBtn.disabled = false; - this.pauseBtn.disabled = true; - if (this.animationId) { - cancelAnimationFrame(this.animationId); - } - } - - reset() { - this.pause(); - this.scrollPosition = 0; - this.teleprompterContainer.scrollTop = 0; - } - - animateScroll() { - if (!this.isPlaying) return; - - const contentHeight = this.teleprompterContent.scrollHeight; - const containerHeight = this.teleprompterContainer.offsetHeight; - - this.scrollPosition += this.scrollSpeed; - - if (this.scrollPosition > contentHeight) { - this.scrollPosition = 0; - } - - this.teleprompterContainer.scrollTop = this.scrollPosition; - - this.animationId = requestAnimationFrame(() => this.animateScroll()); - } - - toggleFullscreen() { - if (!document.fullscreenElement) { - this.enterFullscreen(); - } else { - this.exitFullscreen(); - } - } - - enterFullscreen() { - const element = this.displayArea; - - if (element.requestFullscreen) { - element.requestFullscreen(); - } else if (element.webkitRequestFullscreen) { - element.webkitRequestFullscreen(); - } else if (element.msRequestFullscreen) { - element.msRequestFullscreen(); - } else if (element.mozRequestFullScreen) { - element.mozRequestFullScreen(); - } - } - - exitFullscreen() { - if (document.exitFullscreen) { - document.exitFullscreen(); - } else if (document.webkitExitFullscreen) { - document.webkitExitFullscreen(); - } else if (document.msExitFullscreen) { - document.msExitFullscreen(); - } else if (document.mozCancelFullScreen) { - document.mozCancelFullScreen(); - } - } - - handleFullscreenChange() { - const isFullscreen = !!( - document.fullscreenElement || - document.webkitFullscreenElement || - document.mozFullScreenElement || - document.msFullscreenElement - ); - - if (isFullscreen) { - this.displayArea.classList.add('fullscreen'); - document.body.classList.add('fullscreen'); - this.floatingControls.style.display = 'flex'; - this.fullscreenBtn.innerHTML = ` - - - - 退出全屏 - `; - } else { - this.displayArea.classList.remove('fullscreen'); - document.body.classList.remove('fullscreen'); - this.floatingControls.style.display = 'none'; - this.fullscreenBtn.innerHTML = ` - - - - 全屏 - `; - } - } - - togglePlayPause() { - if (this.isPlaying) { - this.pause(); - this.updatePlayButtons(false); - } else { - this.play(); - this.updatePlayButtons(true); - } - } - - updatePlayButtons(isPlaying) { - const playIcon = ` - - - - `; - const pauseIcon = ` - - - - - `; - - if (isPlaying) { - this.floatPlayBtn.innerHTML = pauseIcon; - } else { - this.floatPlayBtn.innerHTML = playIcon; - } - } - - play() { - if (!this.isPlaying) { - this.isPlaying = true; - this.playBtn.disabled = true; - this.pauseBtn.disabled = false; - this.updatePlayButtons(true); - this.animateScroll(); - } - } - - pause() { - this.isPlaying = false; - this.playBtn.disabled = false; - this.pauseBtn.disabled = true; - this.updatePlayButtons(false); - if (this.animationId) { - cancelAnimationFrame(this.animationId); - } - } - - // 键盘快捷键 - setupKeyboardShortcuts() { - document.addEventListener('keydown', (e) => { - switch(e.code) { - case 'Space': - e.preventDefault(); - if (this.isPlaying) { - this.pause(); - } else { - this.play(); - } - break; - case 'KeyR': - if (e.ctrlKey || e.metaKey) { - e.preventDefault(); - this.reset(); - } - break; - } - }); - } -} - -// 初始化应用 -document.addEventListener('DOMContentLoaded', () => { - const teleprompter = new PDFTeleprompter(); - teleprompter.setupKeyboardShortcuts(); -}); - -// 添加触摸手势支持 -let touchStartY = 0; -document.addEventListener('touchstart', (e) => { - touchStartY = e.touches[0].clientY; -}); - -document.addEventListener('touchend', (e) => { - const touchEndY = e.changedTouches[0].clientY; - const diff = touchStartY - touchEndY; - - if (Math.abs(diff) > 50) { - // 滑动控制滚动速度 - const speedSlider = document.getElementById('speedSlider'); - const currentSpeed = parseInt(speedSlider.value); - - if (diff > 0 && currentSpeed < 10) { - speedSlider.value = currentSpeed + 1; - } else if (diff < 0 && currentSpeed > 1) { - speedSlider.value = currentSpeed - 1; - } - - speedSlider.dispatchEvent(new Event('input')); - } -}); \ No newline at end of file diff --git a/pdf提词器/styles.css b/pdf提词器/styles.css deleted file mode 100644 index 7a3fb7e..0000000 --- a/pdf提词器/styles.css +++ /dev/null @@ -1,407 +0,0 @@ -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -body { - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); - min-height: 100vh; - color: #333; -} - -.container { - max-width: 1200px; - margin: 0 auto; - padding: 20px; -} - -header { - text-align: center; - margin-bottom: 40px; - color: white; -} - -h1 { - font-size: 2.5rem; - margin-bottom: 10px; - font-weight: 300; -} - -.subtitle { - font-size: 1.1rem; - opacity: 0.9; -} - -.upload-section { - margin-bottom: 30px; -} - -.upload-area { - border: 2px dashed rgba(255, 255, 255, 0.5); - border-radius: 15px; - padding: 60px 20px; - text-align: center; - cursor: pointer; - transition: all 0.3s ease; - background: rgba(255, 255, 255, 0.1); - backdrop-filter: blur(10px); -} - -.upload-area:hover { - border-color: rgba(255, 255, 255, 0.8); - background: rgba(255, 255, 255, 0.2); - transform: translateY(-2px); -} - -.upload-area.dragover { - border-color: white; - background: rgba(255, 255, 255, 0.3); - transform: scale(1.02); -} - -.upload-icon { - width: 60px; - height: 60px; - color: white; - margin-bottom: 15px; -} - -.upload-text { - color: white; - font-size: 1.2rem; - font-weight: 300; -} - -.controls { - background: white; - border-radius: 15px; - padding: 30px; - margin-bottom: 30px; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - display: flex; - align-items: center; - justify-content: space-between; - flex-wrap: wrap; - gap: 20px; -} - -.control-group { - display: flex; - align-items: center; - gap: 15px; -} - -.control-group label { - font-weight: 500; - color: #555; -} - -input[type="range"] { - width: 150px; - height: 6px; - border-radius: 3px; - background: #ddd; - outline: none; - -webkit-appearance: none; -} - -input[type="range"]::-webkit-slider-thumb { - -webkit-appearance: none; - width: 18px; - height: 18px; - border-radius: 50%; - background: #667eea; - cursor: pointer; -} - -#speedValue { - font-weight: bold; - color: #667eea; - min-width: 20px; -} - -.btn { - padding: 12px 24px; - border: none; - border-radius: 8px; - font-size: 1rem; - cursor: pointer; - transition: all 0.3s ease; - font-weight: 500; -} - -.btn.primary { - background: #667eea; - color: white; -} - -.btn.primary:hover { - background: #5a6fd8; - transform: translateY(-1px); -} - -.btn.secondary { - background: #f1f3f4; - color: #333; -} - -.btn.secondary:hover:not(:disabled) { - background: #e8eaed; - transform: translateY(-1px); -} - -.btn:disabled { - opacity: 0.5; - cursor: not-allowed; -} - -.display-area { - background: white; - border-radius: 15px; - overflow: hidden; - box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); - margin: 20px; - max-height: 70vh; -} - -.teleprompter-container { - height: 500px; - overflow-y: auto; - overflow-x: hidden; - position: relative; - background: #000; - cursor: grab; - scrollbar-width: thin; - scrollbar-color: #666 #222; -} - -.teleprompter-container::-webkit-scrollbar { - width: 8px; -} - -.teleprompter-container::-webkit-scrollbar-track { - background: #222; -} - -.teleprompter-container::-webkit-scrollbar-thumb { - background: #666; - border-radius: 4px; -} - -.teleprompter-container::-webkit-scrollbar-thumb:hover { - background: #888; -} - -.teleprompter-container:active { - cursor: grabbing; -} - -.teleprompter-content { - padding: 20px; - transform: scaleX(-1); - transition: transform 0.3s ease; - min-height: 100%; -} - -.teleprompter-content img { - width: 100%; - height: auto; - display: block; - margin-bottom: 20px; - border-radius: 8px; - box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3); -} - -/* 全屏模式无边框 */ -.display-area.fullscreen { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: 999; - border-radius: 0; - margin: 0; - max-height: none; -} - -.display-area.fullscreen .teleprompter-container { - height: 100vh; - border-radius: 0; - margin: 0; - padding: 0; -} - -.display-area.fullscreen .teleprompter-content { - padding: 0; -} - -.display-area.fullscreen .teleprompter-content img { - border-radius: 0; - box-shadow: none; - margin-bottom: 0; -} - -/* 滚动提示 */ -.scroll-indicator { - position: absolute; - right: 10px; - top: 50%; - transform: translateY(-50%); - background: rgba(0, 0, 0, 0.5); - color: white; - padding: 8px 12px; - border-radius: 20px; - font-size: 12px; - opacity: 0; - transition: opacity 0.3s ease; - pointer-events: none; - z-index: 10; -} - -.teleprompter-container:hover .scroll-indicator { - opacity: 1; -} - -.loading { - text-align: center; - color: white; - padding: 40px; -} - -.spinner { - width: 40px; - height: 40px; - border: 4px solid rgba(255, 255, 255, 0.3); - border-top: 4px solid white; - border-radius: 50%; - animation: spin 1s linear infinite; - margin: 0 auto 20px; -} - -@keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } -} - -.btn-icon { - width: 16px; - height: 16px; - margin-right: 8px; - vertical-align: middle; -} - -.floating-controls { - position: fixed; - top: 20px; - right: 20px; - background: rgba(0, 0, 0, 0.8); - backdrop-filter: blur(10px); - border-radius: 15px; - padding: 15px; - display: flex; - align-items: center; - gap: 10px; - z-index: 1000; - transition: all 0.3s ease; - box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); -} - -.floating-controls:hover { - background: rgba(0, 0, 0, 0.9); - transform: translateY(-2px); -} - -.float-btn { - width: 45px; - height: 45px; - border: none; - border-radius: 50%; - background: rgba(255, 255, 255, 0.2); - color: white; - cursor: pointer; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.3s ease; - backdrop-filter: blur(5px); -} - -.float-btn:hover { - background: rgba(255, 255, 255, 0.3); - transform: scale(1.1); -} - -.float-btn svg { - width: 20px; - height: 20px; -} - -.float-speed-control { - display: flex; - align-items: center; - gap: 8px; - padding: 0 10px; -} - -.float-speed-control input[type="range"] { - width: 100px; - height: 4px; - background: rgba(255, 255, 255, 0.3); -} - -.float-speed-control input[type="range"]::-webkit-slider-thumb { - width: 14px; - height: 14px; - background: white; -} - -#floatSpeedValue { - color: white; - font-size: 0.9rem; - font-weight: bold; - min-width: 25px; -} - -/* 全屏模式样式 */ -.display-area.fullscreen { - position: fixed; - top: 0; - left: 0; - width: 100vw; - height: 100vh; - z-index: 999; - border-radius: 0; - margin: 0; -} - -.display-area.fullscreen .teleprompter-container { - height: 100vh; - border-radius: 0; -} - -/* 全屏时隐藏原控制面板 */ -body.fullscreen .controls { - display: none !important; -} - -@media (max-width: 768px) { - .controls { - flex-direction: column; - align-items: stretch; - } - - .control-group { - justify-content: center; - } - - h1 { - font-size: 2rem; - } - - .teleprompter-container { - height: 400px; - } -} \ No newline at end of file From 8af1960081dc4e848d3c63e0a40735f953f34116 Mon Sep 17 00:00:00 2001 From: ElmGates <396832647@qq.com> Date: Sat, 27 Dec 2025 13:09:30 +0800 Subject: [PATCH 6/6] Delete README.md --- README.md | 10 ---------- 1 file changed, 10 deletions(-) delete mode 100644 README.md diff --git a/README.md b/README.md deleted file mode 100644 index 1570652..0000000 --- a/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# 提词器源码 -两种提词器网页的源码,包含文本提词器和pdf提词器,支持直接在本地使用,但是可能需要联网。 - -# 使用方法 -1.直接下载源码,本地浏览器打开即可,需要联网,部分js使用的是网络链接。 -2.部署到自己的服务器,只要支持http访问即可。 -3.使用Cloudflare Pages部署即可。 - -# 版权说明 -本项目完全开源,可用于个人,商用请署名,二次开发请署名。