feat: Migrate project to TypeScript by adding configuration, dependencies, and converting preload script.
This commit is contained in:
+28
-15
@@ -1,5 +1,6 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
@@ -7,9 +8,12 @@
|
|||||||
<!-- Premium Fonts -->
|
<!-- Premium Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=JetBrains+Mono:wght@400&display=swap" rel="stylesheet">
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;700&family=JetBrains+Mono:wght@400&display=swap"
|
||||||
|
rel="stylesheet">
|
||||||
<link rel="stylesheet" href="styles.css">
|
<link rel="stylesheet" href="styles.css">
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<!-- Main Scroll Area -->
|
<!-- Main Scroll Area -->
|
||||||
@@ -32,7 +36,9 @@
|
|||||||
<button id="btn-open" class="icon-btn" title="Open File">
|
<button id="btn-open" class="icon-btn" title="Open File">
|
||||||
<!-- Folder Icon -->
|
<!-- Folder Icon -->
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||||
<path d="M4 6C4 4.89543 4.89543 4 6 4H10L12 6H18C19.1046 6 20 6.89543 20 8V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path
|
||||||
|
d="M4 6C4 4.89543 4.89543 4 6 4H10L12 6H18C19.1046 6 20 6.89543 20 8V18C20 19.1046 19.1046 20 18 20H6C4.89543 20 4 19.1046 4 18V6Z"
|
||||||
|
stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -41,14 +47,18 @@
|
|||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<button id="btn-play" class="action-btn" title="Start/Stop Scrolling">
|
<button id="btn-play" class="action-btn" title="Start/Stop Scrolling">
|
||||||
<!-- Play Icon -->
|
<!-- Play Icon -->
|
||||||
<svg id="icon-play" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
<svg id="icon-play" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||||
<path d="M5 3L19 12L5 21V3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
<path d="M5 3L19 12L5 21V3Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
<!-- Pause Icon (Initially hidden) -->
|
<!-- Pause Icon (Initially hidden) -->
|
||||||
<svg id="icon-pause" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon" style="display: none;">
|
<svg id="icon-pause" width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon"
|
||||||
<path d="M10 4H6V20H10V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
style="display: none;">
|
||||||
<path d="M18 4H14V20H18V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" fill="currentColor"/>
|
<path d="M10 4H6V20H10V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" fill="currentColor" />
|
||||||
|
<path d="M18 4H14V20H18V4Z" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" fill="currentColor" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,20 +77,23 @@
|
|||||||
<input type="range" id="input-size" min="20" max="150" value="60">
|
<input type="range" id="input-size" min="20" max="150" value="60">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="divider"></div>
|
<div class="divider"></div>
|
||||||
|
|
||||||
<div class="control-group">
|
<div class="control-group">
|
||||||
<button id="btn-reset" class="icon-btn" title="Reset Position">
|
<button id="btn-reset" class="icon-btn" title="Reset Position">
|
||||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" class="icon">
|
||||||
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
<path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" stroke="currentColor"
|
||||||
<path d="M3 3v5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
|
||||||
|
<path d="M3 3v5h5" stroke="currentColor" stroke-width="2" stroke-linecap="round"
|
||||||
|
stroke-linejoin="round" />
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="./node_modules/marked/marked.min.js"></script>
|
<script src="./node_modules/marked/marked.min.js"></script>
|
||||||
<script src="renderer.js"></script>
|
<script src="dist/renderer.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
Generated
+49
-8
@@ -12,7 +12,10 @@
|
|||||||
"marked": "^15.0.12"
|
"marked": "^15.0.12"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^39.2.6"
|
"@types/marked": "^5.0.2",
|
||||||
|
"@types/node": "^25.0.1",
|
||||||
|
"electron": "^39.2.6",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@electron/get": {
|
"node_modules/@electron/get": {
|
||||||
@@ -93,14 +96,21 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/marked": {
|
||||||
|
"version": "5.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/marked/-/marked-5.0.2.tgz",
|
||||||
|
"integrity": "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "22.19.2",
|
"version": "25.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.1.tgz",
|
||||||
"integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
|
"integrity": "sha512-czWPzKIAXucn9PtsttxmumiQ9N0ok9FrBwgRWrwmVLlp86BrMExzvXRLFYRJ+Ex3g6yqj+KuaxfX1JTgV2lpfg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"undici-types": "~6.21.0"
|
"undici-types": "~7.16.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/responselike": {
|
"node_modules/@types/responselike": {
|
||||||
@@ -307,6 +317,23 @@
|
|||||||
"node": ">= 12.20.55"
|
"node": ">= 12.20.55"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/electron/node_modules/@types/node": {
|
||||||
|
"version": "22.19.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.2.tgz",
|
||||||
|
"integrity": "sha512-LPM2G3Syo1GLzXLGJAKdqoU35XvrWzGJ21/7sgZTUpbkBaOasTj8tjwn6w+hCkqaa1TfJ/w67rJSwYItlJ2mYw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~6.21.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/electron/node_modules/undici-types": {
|
||||||
|
"version": "6.21.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
||||||
|
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/end-of-stream": {
|
"node_modules/end-of-stream": {
|
||||||
"version": "1.4.5",
|
"version": "1.4.5",
|
||||||
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
|
||||||
@@ -848,10 +875,24 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/undici-types": {
|
"node_modules/undici-types": {
|
||||||
"version": "6.21.0",
|
"version": "7.16.0",
|
||||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz",
|
||||||
"integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
|
"integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
|||||||
+7
-3
@@ -1,16 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "teleprompter",
|
"name": "teleprompter",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"main": "main.js",
|
"main": "dist/main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "electron ."
|
"build": "tsc",
|
||||||
|
"start": "npm run build && electron ."
|
||||||
},
|
},
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"description": "",
|
"description": "",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^39.2.6"
|
"@types/marked": "^5.0.2",
|
||||||
|
"@types/node": "^25.0.1",
|
||||||
|
"electron": "^39.2.6",
|
||||||
|
"typescript": "^5.9.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"marked": "^15.0.12"
|
"marked": "^15.0.12"
|
||||||
|
|||||||
@@ -1,6 +0,0 @@
|
|||||||
const { contextBridge, ipcRenderer } = require('electron');
|
|
||||||
|
|
||||||
contextBridge.exposeInMainWorld('electronAPI', {
|
|
||||||
openFileDialog: () => ipcRenderer.invoke('open-file-dialog'),
|
|
||||||
readFile: (filePath) => ipcRenderer.invoke('read-file', filePath)
|
|
||||||
});
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
const { app, BrowserWindow, ipcMain, dialog } = require('electron');
|
import { app, BrowserWindow, ipcMain, dialog } from 'electron';
|
||||||
const path = require('path');
|
import * as path from 'path';
|
||||||
const fs = require('fs');
|
import * as fs from 'fs';
|
||||||
|
|
||||||
function createWindow() {
|
function createWindow() {
|
||||||
const win = new BrowserWindow({
|
const win = new BrowserWindow({
|
||||||
@@ -17,7 +17,8 @@ function createWindow() {
|
|||||||
titleBarStyle: 'hiddenInset' // Looks cleaner on macOS, normal on others
|
titleBarStyle: 'hiddenInset' // Looks cleaner on macOS, normal on others
|
||||||
});
|
});
|
||||||
|
|
||||||
win.loadFile('index.html');
|
win.loadFile(path.join(__dirname, '../index.html'));
|
||||||
|
// Note: index.html is in the root, dist/main.js is in dist/. So we need to go up one level.
|
||||||
}
|
}
|
||||||
|
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
@@ -52,7 +53,7 @@ ipcMain.handle('open-file-dialog', async () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMain.handle('read-file', async (event, filePath) => {
|
ipcMain.handle('read-file', async (event, filePath: string) => {
|
||||||
try {
|
try {
|
||||||
const content = fs.readFileSync(filePath, 'utf-8');
|
const content = fs.readFileSync(filePath, 'utf-8');
|
||||||
return content;
|
return content;
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
import { contextBridge, ipcRenderer } from 'electron';
|
||||||
|
|
||||||
|
contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
|
openFileDialog: () => ipcRenderer.invoke('open-file-dialog'),
|
||||||
|
readFile: (filePath: string) => ipcRenderer.invoke('read-file', filePath)
|
||||||
|
});
|
||||||
@@ -1,18 +1,31 @@
|
|||||||
const btnHeroOpen = document.getElementById('btn-hero-open');
|
interface ElectronAPI {
|
||||||
const btnOpen = document.getElementById('btn-open');
|
openFileDialog: () => Promise<string | null>;
|
||||||
const btnPlay = document.getElementById('btn-play');
|
readFile: (path: string) => Promise<string>;
|
||||||
const btnReset = document.getElementById('btn-reset');
|
}
|
||||||
const inputSpeed = document.getElementById('input-speed');
|
|
||||||
const inputSize = document.getElementById('input-size');
|
interface Window {
|
||||||
const markdownOutput = document.getElementById('markdown-output');
|
electronAPI: ElectronAPI;
|
||||||
const placeholder = document.getElementById('placeholder');
|
}
|
||||||
const scrollContainer = document.getElementById('scroll-container');
|
|
||||||
const iconPlay = document.getElementById('icon-play');
|
declare const marked: {
|
||||||
const iconPause = document.getElementById('icon-pause');
|
parse: (content: string) => string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const btnHeroOpen = document.getElementById('btn-hero-open') as HTMLButtonElement;
|
||||||
|
const btnOpen = document.getElementById('btn-open') as HTMLButtonElement;
|
||||||
|
const btnPlay = document.getElementById('btn-play') as HTMLButtonElement;
|
||||||
|
const btnReset = document.getElementById('btn-reset') as HTMLButtonElement;
|
||||||
|
const inputSpeed = document.getElementById('input-speed') as HTMLInputElement;
|
||||||
|
const inputSize = document.getElementById('input-size') as HTMLInputElement;
|
||||||
|
const markdownOutput = document.getElementById('markdown-output') as HTMLDivElement;
|
||||||
|
const placeholder = document.getElementById('placeholder') as HTMLDivElement;
|
||||||
|
const scrollContainer = document.getElementById('scroll-container') as HTMLDivElement;
|
||||||
|
const iconPlay = document.getElementById('icon-play') as HTMLElement;
|
||||||
|
const iconPause = document.getElementById('icon-pause') as HTMLElement;
|
||||||
|
|
||||||
let isScrolling = false;
|
let isScrolling = false;
|
||||||
let scrollAccumulator = 0;
|
let scrollAccumulator = 0;
|
||||||
let animationId;
|
let animationId: number;
|
||||||
let scrollSpeed = parseInt(inputSpeed.value, 10);
|
let scrollSpeed = parseInt(inputSpeed.value, 10);
|
||||||
|
|
||||||
// Initialize
|
// Initialize
|
||||||
@@ -28,7 +41,7 @@ function setupEventListeners() {
|
|||||||
btnPlay.addEventListener('click', toggleScrolling);
|
btnPlay.addEventListener('click', toggleScrolling);
|
||||||
|
|
||||||
// Spacebar to toggle
|
// Spacebar to toggle
|
||||||
document.addEventListener('keydown', (e) => {
|
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||||
if (e.code === 'Space') {
|
if (e.code === 'Space') {
|
||||||
e.preventDefault(); // Prevent page down scroll
|
e.preventDefault(); // Prevent page down scroll
|
||||||
toggleScrolling();
|
toggleScrolling();
|
||||||
@@ -40,27 +53,31 @@ function setupEventListeners() {
|
|||||||
scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
|
scrollContainer.scrollTo({ top: 0, behavior: 'smooth' });
|
||||||
});
|
});
|
||||||
|
|
||||||
inputSpeed.addEventListener('input', (e) => {
|
inputSpeed.addEventListener('input', (e: Event) => {
|
||||||
scrollSpeed = parseInt(e.target.value, 10);
|
const target = e.target as HTMLInputElement;
|
||||||
|
scrollSpeed = parseInt(target.value, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
inputSize.addEventListener('input', (e) => {
|
inputSize.addEventListener('input', (e: Event) => {
|
||||||
updateFontSize(e.target.value);
|
const target = e.target as HTMLInputElement;
|
||||||
|
updateFontSize(target.value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openFile() {
|
async function openFile() {
|
||||||
const filePath = await window.electronAPI.openFileDialog();
|
// Explicitly cast window if interface merging fails in some environments
|
||||||
|
const api = (window as unknown as Window).electronAPI;
|
||||||
|
const filePath = await api.openFileDialog();
|
||||||
if (filePath) {
|
if (filePath) {
|
||||||
const content = await window.electronAPI.readFile(filePath);
|
const content = await api.readFile(filePath);
|
||||||
renderMarkdown(content);
|
renderMarkdown(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderMarkdown(content) {
|
function renderMarkdown(content: string) {
|
||||||
if (!content) return;
|
if (!content) return;
|
||||||
|
|
||||||
// Using marked from global scope (loaded via script tag)
|
// Using marked from global scope
|
||||||
const html = marked.parse(content);
|
const html = marked.parse(content);
|
||||||
markdownOutput.innerHTML = html;
|
markdownOutput.innerHTML = html;
|
||||||
|
|
||||||
@@ -72,7 +89,7 @@ function renderMarkdown(content) {
|
|||||||
scrollContainer.scrollTop = 0;
|
scrollContainer.scrollTop = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateFontSize(size) {
|
function updateFontSize(size: string) {
|
||||||
markdownOutput.style.fontSize = `${size}px`;
|
markdownOutput.style.fontSize = `${size}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,31 +123,17 @@ function stopScrolling() {
|
|||||||
function step() {
|
function step() {
|
||||||
if (!isScrolling) return;
|
if (!isScrolling) return;
|
||||||
|
|
||||||
// Calculate scroll amount based on speed
|
|
||||||
// Map 1-100 to sensible range.
|
|
||||||
// Let's say max speed (100) is ~300 pixels per second? ~5px/frame at 60fps
|
|
||||||
// Min speed (1) is ~10 pixels per second? ~0.16px/frame
|
|
||||||
|
|
||||||
// Formula: (speed / 100) * maxPixelsPerFrame
|
|
||||||
// Adding a base speed so 1 isn't 0.
|
|
||||||
|
|
||||||
const maxSpeedValues = 5.0;
|
const maxSpeedValues = 5.0;
|
||||||
const minSpeedValues = 0.2;
|
const minSpeedValues = 0.2;
|
||||||
|
|
||||||
// Exponential curve for better control at lower speeds
|
|
||||||
// Normalized speed 0-1
|
|
||||||
const normalizedSpeed = (scrollSpeed - 1) / 99;
|
const normalizedSpeed = (scrollSpeed - 1) / 99;
|
||||||
|
|
||||||
// Linear interpolation? Or quadratic? Quadratic feels "acceleration"
|
|
||||||
// Let's stick to linear for predictability first, user says "simple"
|
|
||||||
const pixelsPerFrame = minSpeedValues + (normalizedSpeed * (maxSpeedValues - minSpeedValues));
|
const pixelsPerFrame = minSpeedValues + (normalizedSpeed * (maxSpeedValues - minSpeedValues));
|
||||||
|
|
||||||
scrollAccumulator += pixelsPerFrame;
|
scrollAccumulator += pixelsPerFrame;
|
||||||
const pixelsToScroll = Math.floor(scrollAccumulator);
|
const pixelsToScroll = Math.floor(scrollAccumulator);
|
||||||
|
|
||||||
if (pixelsToScroll >= 1) {
|
if (pixelsToScroll >= 1) {
|
||||||
// Stop if at bottom
|
|
||||||
// Use a small buffer (1px) for float inconsistencies
|
|
||||||
if (Math.ceil(scrollContainer.scrollTop + scrollContainer.clientHeight) >= scrollContainer.scrollHeight) {
|
if (Math.ceil(scrollContainer.scrollTop + scrollContainer.clientHeight) >= scrollContainer.scrollHeight) {
|
||||||
stopScrolling();
|
stopScrolling();
|
||||||
return;
|
return;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user