Published
Edited
May 30, 2022
1 fork
Importers
16 stars
Also listed in…
Observable Things
Insert cell
Insert cell
Insert cell
Insert cell
viewof example1 = snapRange(
// Range input
Inputs.range([0, 4], {label: 'Number', step: .01}),
// Snap offsets (shorthand)
[0, 1, 2, 3, 4],
// +/- range around offsets; required if shorthand form is used.
// Expands offsets to [start, target, end], e.g.: [[-.2, 0, .2], [.8, 1, 1.2], ...]
{ margin: .2 }
)
Insert cell
example1
Insert cell
Insert cell
viewof example2 = snapRange(
Inputs.range([0, 4], {label: 'Number', step: .01}),
// custom ranges, each defining start, target, end
[[1, 1, 1.5], [2, 2, 2.5], [3, 3, 3.5]]
)
Insert cell
example2
Insert cell
Insert cell
viewof example3 = snapRange(html`<input type=range min=0 max=1 step=0.01>`, [0, .5, 1], {margin: .1})
Insert cell
example3
Insert cell
Insert cell
viewof example4 = snapRange(Inputs.range([0, 100]), null, {
transform: value => Math.round(value ** .5) ** 2
})
Insert cell
example4
Insert cell
Insert cell
function snapRange(input, values, options = {}) {
const {
invalidation: invalidated = invalidation,
margin = 0,
transform = snap(values, margin),
} = options;
const isRange = n => n.localName === 'input' && n.type === 'range';
let pointer = false, target, disabled = false;
const handlers = {
input: e => {
if(!e.isTrusted || !pointer || disabled) return;
target = transform(input.value);
if(target !== undefined && target !== input.value) input.value = target;
},
pointerdown: e => {
pointer = isRange(e.target);
},
keydown: e => {
disabled = true;
},
keyup: e => {
disabled = false;
},
change: e => {
// Firefox fires an additional input event after pointerup.
pointer = false;
},
};
for(const [name, fn] of Object.entries(handlers)) {
input.addEventListener(name, fn);
invalidated.then(() => input.removeEventListener(name, fn));
}
return input;

}
Insert cell
function snap(values, margin) {
const ranges = values.map(v => Array.isArray(v) ? v : [v - margin, v, v + margin]);
return value => {
for(const [start, target, end] of ranges) {
if(value < start || value > end) continue;
return target;
}
}
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more