mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-11-27 21:47:59 +01:00
document and optimize
This commit is contained in:
parent
694bc31e50
commit
0877ac685b
3 changed files with 84 additions and 43 deletions
|
@ -8,15 +8,10 @@
|
|||
* <input type="dualrange" min="0" max="100" valuemin="0" valuemax="100">
|
||||
*/
|
||||
|
||||
import { lerp } from './utils/lerp';
|
||||
import { $$ } from './utils/dom';
|
||||
|
||||
function lerp(delta: number, from: number, to: number): number {
|
||||
if (delta >= 1) { return to; }
|
||||
else if (delta <= 0) { return from; }
|
||||
|
||||
return from + (to - from) * delta;
|
||||
}
|
||||
|
||||
// Make a given slider head draggable.
|
||||
function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty: string, limitProperty: string) {
|
||||
const parent = el.parentElement;
|
||||
|
||||
|
@ -24,11 +19,41 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty:
|
|||
return;
|
||||
}
|
||||
|
||||
// Initialize variables and constants.
|
||||
const inputEvent = new InputEvent('input');
|
||||
let minPos = 0;
|
||||
let maxPos = 0;
|
||||
let cachedMin = 0;
|
||||
let cachedMax = 0;
|
||||
let cachedValue = 0;
|
||||
let cachedLimit = 0;
|
||||
let curValue = 0;
|
||||
let dragging = false;
|
||||
|
||||
// Clamps the slider head value to not cross over the other slider head's value.
|
||||
function clampValue(value: number): number {
|
||||
if (cachedValue >= cachedLimit && value < cachedLimit) {
|
||||
return cachedLimit;
|
||||
}
|
||||
else if (cachedValue < cachedLimit && value >= cachedLimit) {
|
||||
return cachedLimit - 1; // Offset by 1 to ensure stored value is less than limit.
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
// Utility accessor to get the minimum value of dualrange.
|
||||
function getMin(): number {
|
||||
return Number(dataEl.getAttribute('min') || '0');
|
||||
}
|
||||
|
||||
// Utility accessor to get the maximum value of dualrange.
|
||||
function getMax(): number {
|
||||
return Number(dataEl.getAttribute('max') || '0');
|
||||
}
|
||||
|
||||
// Initializes cached variables. Should be used
|
||||
// when the pointer event begins.
|
||||
function initVars() {
|
||||
if (!parent) { return; }
|
||||
|
||||
|
@ -36,32 +61,14 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty:
|
|||
|
||||
minPos = rect.x;
|
||||
maxPos = rect.x + rect.width - el.clientWidth;
|
||||
cachedMin = getMin();
|
||||
cachedMax = getMax();
|
||||
cachedValue = Number(dataEl.getAttribute(valueProperty) || '0');
|
||||
cachedLimit = Number(dataEl.getAttribute(limitProperty) || '0');
|
||||
curValue = Number(dataEl.getAttribute(valueProperty) || '0');
|
||||
}
|
||||
|
||||
function clampValue(value: number): number {
|
||||
const storedValue = Number(dataEl.getAttribute(valueProperty) || '0');
|
||||
const limitValue = Number(dataEl.getAttribute(limitProperty) || '0');
|
||||
|
||||
if (storedValue >= limitValue && value < limitValue) {
|
||||
return limitValue;
|
||||
}
|
||||
else if (storedValue < limitValue && value >= limitValue) {
|
||||
return limitValue - 1; // Offset by 1 to ensure stored value is less than limit.
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function getMin(): number {
|
||||
return Number(dataEl.getAttribute('min') || '0');
|
||||
}
|
||||
|
||||
function getMax(): number {
|
||||
return Number(dataEl.getAttribute('max') || '0');
|
||||
}
|
||||
|
||||
// Define functions to control the drag behavior of the slider.
|
||||
// Called during pointer movement.
|
||||
function dragMove(e: PointerEvent) {
|
||||
if (!dragging) { return; }
|
||||
|
||||
|
@ -69,40 +76,40 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty:
|
|||
|
||||
let desiredPos = e.clientX;
|
||||
|
||||
if (desiredPos > maxPos) {
|
||||
desiredPos = maxPos;
|
||||
}
|
||||
else if (desiredPos < minPos) {
|
||||
desiredPos = minPos;
|
||||
}
|
||||
|
||||
// `lerp` cleverly clamps the value between min and max,
|
||||
// so no need for any explicit checks for that here, only
|
||||
// the crossover check is required.
|
||||
curValue = clampValue(
|
||||
lerp(
|
||||
(desiredPos - minPos) / (maxPos - minPos),
|
||||
getMin(),
|
||||
getMax()
|
||||
cachedMin,
|
||||
cachedMax
|
||||
)
|
||||
);
|
||||
|
||||
desiredPos = lerp(curValue / getMax(), minPos, maxPos);
|
||||
// Same here, lerp clamps the value so it doesn't get out
|
||||
// of the slider boundary.
|
||||
desiredPos = lerp(curValue / cachedMax, minPos, maxPos);
|
||||
|
||||
el.style.left = `${desiredPos}px`;
|
||||
|
||||
dataEl.setAttribute(valueProperty, curValue.toString());
|
||||
dataEl.dispatchEvent(new InputEvent('input'));
|
||||
dataEl.dispatchEvent(inputEvent);
|
||||
}
|
||||
|
||||
// Called when the pointer is let go of.
|
||||
function dragEnd(e: PointerEvent) {
|
||||
if (!dragging) { return; }
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
dataEl.setAttribute(valueProperty, curValue.toString());
|
||||
dataEl.dispatchEvent(new InputEvent('input'));
|
||||
dataEl.dispatchEvent(inputEvent);
|
||||
|
||||
dragging = false;
|
||||
}
|
||||
|
||||
// Called when the slider head is clicked or tapped.
|
||||
function dragBegin(e: PointerEvent) {
|
||||
if (!parent) { return; }
|
||||
|
||||
|
@ -112,7 +119,7 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty:
|
|||
dragging = true;
|
||||
}
|
||||
|
||||
// Set initial position;
|
||||
// Set the initial variables and position;
|
||||
initVars();
|
||||
el.style.left = `${lerp(curValue / getMax(), minPos, maxPos)}px`;
|
||||
|
||||
|
@ -122,6 +129,10 @@ function setupDrag(el: HTMLDivElement, dataEl: HTMLInputElement, valueProperty:
|
|||
window.addEventListener('pointermove', dragMove);
|
||||
}
|
||||
|
||||
// Sets up the slider input element.
|
||||
// Creates `div` elements for presentation, hides the
|
||||
// original `input` element. The logic is that
|
||||
// we use divs for presentation, and input for data storage.
|
||||
function setupSlider(el: HTMLInputElement) {
|
||||
const parent = el.parentElement;
|
||||
|
||||
|
@ -155,6 +166,7 @@ function setupSlider(el: HTMLInputElement) {
|
|||
setupDrag(maxHead, el, 'valuemax', 'valuemin');
|
||||
}
|
||||
|
||||
// Sets up all sliders currently on the page.
|
||||
function setupSliders() {
|
||||
$$<HTMLInputElement>('input[type="dualrange"]').forEach(el => {
|
||||
setupSlider(el);
|
||||
|
|
17
assets/js/utils/__tests__/lerp.spec.ts
Normal file
17
assets/js/utils/__tests__/lerp.spec.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { lerp } from '../lerp';
|
||||
|
||||
describe('Linear interpolation', () => {
|
||||
describe('lerp', () => {
|
||||
it('should interpolate the min-max range based on a delta', () => {
|
||||
expect(lerp(0.5, 0, 100)).toEqual(50);
|
||||
expect(lerp(0.75, 0, 100)).toEqual(75);
|
||||
});
|
||||
|
||||
it('should clamp the value between min and max', () => {
|
||||
expect(lerp(-999, 0, 100)).toEqual(0);
|
||||
expect(lerp(0, 0, 100)).toEqual(0);
|
||||
expect(lerp(999, 0, 100)).toEqual(100);
|
||||
expect(lerp(1, 0, 100)).toEqual(100);
|
||||
});
|
||||
});
|
||||
});
|
12
assets/js/utils/lerp.ts
Normal file
12
assets/js/utils/lerp.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
// Simple linear interpolation.
|
||||
// Returns a value between min and max based on a delta.
|
||||
// The delta is a number between 0 and 1.
|
||||
// If the delta is not within the 0-1 range, this function will
|
||||
// clamp the value between min and max, depending on whether
|
||||
// the delta >= 1 or <= 0.
|
||||
export function lerp(delta: number, from: number, to: number): number {
|
||||
if (delta >= 1) { return to; }
|
||||
else if (delta <= 0) { return from; }
|
||||
|
||||
return from + (to - from) * delta;
|
||||
}
|
Loading…
Reference in a new issue