<range-input> (#171)

This commit is contained in:
Jake Archibald
2018-10-02 11:41:07 +01:00
committed by GitHub
parent 248676aa31
commit b25d1eaf86
7 changed files with 249 additions and 39 deletions

View File

@@ -1,5 +1,6 @@
import { h, Component } from 'preact'; import { h, Component } from 'preact';
import { bind } from '../../lib/util'; import { bind } from '../../lib/util';
import '../../components/output/custom-els/RangeInput';
interface EncodeOptions { interface EncodeOptions {
quality: number; quality: number;
@@ -35,9 +36,8 @@ export default function qualityOption(opts: QualityOptionArg = {}) {
<div> <div>
<label> <label>
Quality: Quality:
<input <range-input
name="quality" name="quality"
type="range"
min={min} min={min}
max={max} max={max}
step={step || 'any'} step={step || 'any'}

View File

@@ -50,9 +50,8 @@ export default class QuantizerOptions extends Component<Props, State> {
</label> </label>
<label style={{ display: options.zx ? 'none' : '' }}> <label style={{ display: options.zx ? 'none' : '' }}>
Palette Colors: Palette Colors:
<input <range-input
name="maxNumColors" name="maxNumColors"
type="range"
min="2" min="2"
max="256" max="256"
value={'' + options.maxNumColors} value={'' + options.maxNumColors}
@@ -61,9 +60,8 @@ export default class QuantizerOptions extends Component<Props, State> {
</label> </label>
<label> <label>
Dithering: Dithering:
<input <range-input
name="dither" name="dither"
type="range"
min="0" min="0"
max="1" max="1"
step="0.01" step="0.01"

View File

@@ -55,12 +55,12 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
filter_type: inputFieldCheckedAsNumber(form.filter_type), filter_type: inputFieldCheckedAsNumber(form.filter_type),
use_sharp_yuv: inputFieldCheckedAsNumber(form.use_sharp_yuv), use_sharp_yuv: inputFieldCheckedAsNumber(form.use_sharp_yuv),
// .value // .value
near_lossless: inputFieldValueAsNumber(form.near_lossless), near_lossless: 100 - inputFieldValueAsNumber(form.near_lossless),
alpha_quality: inputFieldValueAsNumber(form.alpha_quality), alpha_quality: inputFieldValueAsNumber(form.alpha_quality),
alpha_filtering: inputFieldValueAsNumber(form.alpha_filtering), alpha_filtering: inputFieldValueAsNumber(form.alpha_filtering),
sns_strength: inputFieldValueAsNumber(form.sns_strength), sns_strength: inputFieldValueAsNumber(form.sns_strength),
filter_strength: inputFieldValueAsNumber(form.filter_strength), filter_strength: inputFieldValueAsNumber(form.filter_strength),
filter_sharpness: inputFieldValueAsNumber(form.filter_sharpness), filter_sharpness: 7 - inputFieldValueAsNumber(form.filter_sharpness),
pass: inputFieldValueAsNumber(form.pass), pass: inputFieldValueAsNumber(form.pass),
preprocessing: inputFieldValueAsNumber(form.preprocessing), preprocessing: inputFieldValueAsNumber(form.preprocessing),
segments: inputFieldValueAsNumber(form.segments), segments: inputFieldValueAsNumber(form.segments),
@@ -74,9 +74,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
<div> <div>
<label> <label>
Effort: Effort:
<input <range-input
name="lossless_preset" name="lossless_preset"
type="range"
min="0" min="0"
max="9" max="9"
value={'' + determineLosslessQuality(options.quality)} value={'' + determineLosslessQuality(options.quality)}
@@ -85,13 +84,11 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Slight loss: Slight loss:
<input <range-input
class={styles.flipRange}
name="near_lossless" name="near_lossless"
type="range"
min="0" min="0"
max="100" max="100"
value={'' + options.near_lossless} value={'' + (100 - options.near_lossless)}
onChange={this.onChange} onChange={this.onChange}
/> />
</label> </label>
@@ -119,9 +116,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
<div> <div>
<label> <label>
Effort: Effort:
<input <range-input
name="method_input" name="method_input"
type="range"
min="0" min="0"
max="6" max="6"
value={'' + options.method} value={'' + options.method}
@@ -130,9 +126,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Quality: Quality:
<input <range-input
name="quality" name="quality"
type="range"
min="0" min="0"
max="100" max="100"
step="0.01" step="0.01"
@@ -151,9 +146,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Alpha quality: Alpha quality:
<input <range-input
name="alpha_quality" name="alpha_quality"
type="range"
min="0" min="0"
max="100" max="100"
value={'' + options.alpha_quality} value={'' + options.alpha_quality}
@@ -162,9 +156,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Alpha filter quality: Alpha filter quality:
<input <range-input
name="alpha_filtering" name="alpha_filtering"
type="range"
min="0" min="0"
max="2" max="2"
value={'' + options.alpha_filtering} value={'' + options.alpha_filtering}
@@ -173,9 +166,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Spacial noise shaping: Spacial noise shaping:
<input <range-input
name="sns_strength" name="sns_strength"
type="range"
min="0" min="0"
max="100" max="100"
value={'' + options.sns_strength} value={'' + options.sns_strength}
@@ -193,9 +185,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Filter strength: Filter strength:
<input <range-input
name="filter_strength" name="filter_strength"
type="range"
min="0" min="0"
max="100" max="100"
disabled={!!options.autofilter} disabled={!!options.autofilter}
@@ -214,13 +205,11 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Filter sharpness: Filter sharpness:
<input <range-input
class={styles.flipRange}
name="filter_sharpness" name="filter_sharpness"
type="range"
min="0" min="0"
max="7" max="7"
value={'' + options.filter_sharpness} value={'' + (7 - options.filter_sharpness)}
onChange={this.onChange} onChange={this.onChange}
/> />
</label> </label>
@@ -235,9 +224,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Passes: Passes:
<input <range-input
name="pass" name="pass"
type="range"
min="1" min="1"
max="10" max="10"
value={'' + options.pass} value={'' + options.pass}
@@ -258,9 +246,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Segments: Segments:
<input <range-input
name="segments" name="segments"
type="range"
min="1" min="1"
max="4" max="4"
value={'' + options.segments} value={'' + options.segments}
@@ -269,9 +256,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
</label> </label>
<label> <label>
Partitions: Partitions:
<input <range-input
name="partitions" name="partitions"
type="range"
min="0" min="0"
max="3" max="3"
value={'' + options.partitions} value={'' + options.partitions}

View File

@@ -1,6 +1,3 @@
.flip-range {
transform: scaleX(-1);
}
.hide { .hide {
display: none; display: none;
} }

View File

@@ -0,0 +1,115 @@
import { bind } from '../../../../lib/util';
import './styles.css';
const RETARGETED_EVENTS = ['focus', 'blur'];
const UPDATE_EVENTS = ['input', 'change'];
const REFLECTED_PROPERTIES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
const REFLECTED_ATTRIBUTES = ['name', 'min', 'max', 'step', 'value', 'disabled'];
class RangeInputElement extends HTMLElement {
private _input = document.createElement('input');
private _valueDisplayWrapper = document.createElement('div');
private _valueDisplay = document.createElement('span');
private _ignoreChange = false;
static get observedAttributes() {
return REFLECTED_ATTRIBUTES;
}
constructor() {
super();
this._input.type = 'range';
for (const event of RETARGETED_EVENTS) {
this._input.addEventListener(event, this._retargetEvent, true);
}
for (const event of UPDATE_EVENTS) {
this._input.addEventListener(event, this._update, true);
}
}
get labelPrecision(): string {
return this.getAttribute('label-precision') || '';
}
set labelPrecision(precision: string) {
this.setAttribute('label-precision', precision);
}
connectedCallback() {
if (this._input.parentNode !== this) {
this.appendChild(this._input);
this._valueDisplayWrapper.appendChild(this._valueDisplay);
this.appendChild(this._valueDisplayWrapper);
}
}
attributeChangedCallback(name: string, oldValue: string, newValue: string | null) {
if (this._ignoreChange) return;
if (newValue === null) {
this._input.removeAttribute(name);
} else {
this._input.setAttribute(name, newValue);
}
this._reflectAttributes();
this._update();
}
@bind
private _retargetEvent(event: Event) {
event.stopImmediatePropagation();
const retargetted = new Event(event.type, event);
this.dispatchEvent(retargetted);
}
@bind
private _update() {
const value = parseFloat(this.value || '0');
const min = parseFloat(this.min || '0');
const max = parseFloat(this.max || '100');
const labelPrecision = parseFloat(this.labelPrecision || '0');
const percent = 100 * (value - min) / (max - min);
const displayValue = labelPrecision ? value.toPrecision(labelPrecision) :
Math.round(value).toString();
this.style.setProperty('--value-percent', percent + '%');
this._valueDisplay.textContent = displayValue;
}
private _reflectAttributes() {
this._ignoreChange = true;
for (const attributeName in REFLECTED_ATTRIBUTES) {
const attributeValue = this._input.getAttribute(attributeName);
if (attributeValue === null) continue;
this.setAttribute(attributeName, attributeValue);
}
this._ignoreChange = false;
}
}
interface RangeInputElement {
name: string;
min: string;
max: string;
step: string;
value: string;
disabled: boolean;
}
for (const prop of REFLECTED_PROPERTIES) {
Object.defineProperty(RangeInputElement.prototype, prop, {
get() {
return this._input[prop];
},
set(val) {
this._input[prop] = val;
this._reflectAttributes();
this._update();
},
});
}
export default RangeInputElement;
customElements.define('range-input', RangeInputElement);

View File

@@ -0,0 +1,5 @@
declare namespace JSX {
interface IntrinsicElements {
'range-input': HTMLAttributes;
}
}

View File

@@ -0,0 +1,109 @@
range-input {
position: relative;
display: flex;
height: 18px;
width: 130px;
margin: 2px;
font: inherit;
line-height: 16px;
overflow: visible;
}
range-input input {
position: relative;
flex: 1;
width: 100%;
padding: 0 !important;
margin: 0 !important;
background: none;
appearance: none;
-webkit-appearance: none;
outline: none;
}
range-input input::-webkit-slider-runnable-track,
range-input input::-moz-range-track,
range-input input::-ms-track {
appearance: none;
-ms-appearance: none;
-moz-appearance: none;
-webkit-appearance: none;
}
range-input::before {
content: '';
display: block;
position: absolute;
top: 8px;
left: 0;
width: 100%;
height: 2px;
outline: none;
border: none;
background: #eee;
border-radius: 1px;
box-shadow: 0 -.5px 0 rgba(0,0,0,0.3), inset 0 .5px 0 rgba(255,255,255,0.2), 0 .5px 0 rgba(255,255,255,0.3);
background: linear-gradient(#34B9EB, #218ab1) 0/ var(--value-percent, 0%) 100% no-repeat #eee;
}
range-input[disabled]::before {
background: linear-gradient(#aaa, #888) 0/ var(--value-percent, 0%) 100% no-repeat #eee;
}
range-input input::-webkit-slider-thumb {
appearance: none;
-webkit-appearance: none;
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><circle cx="5" cy="5" r="1" fill="#5D509E" /></svg>') center no-repeat #34B9EB;
box-sizing: content-box;
border-radius: 50%;
width: 12px;
height: 12px;
border: none;
box-shadow: 0 0.5px 2px rgba(0,0,0,0.3);
outline: none;
}
range-input[disabled] input::-webkit-slider-thumb {
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><circle cx="5" cy="5" r="1" fill="#666" /></svg>') center no-repeat #999;
}
range-input input:focus::-webkit-slider-thumb {
box-shadow: 0 1px 3px rgba(0,0,0,0.5);
}
range-input > div {
position: absolute;
left: 6px;
right: 6px;
bottom: 0;
height: 0;
overflow: visible;
}
range-input > div > span {
background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" width="32" height="62" fill="none"><path fill="#34B9EB" d="M27.3 27.3C25 29.6 17 35.8 17 43v3c0 3 2.5 5 3.2 5.8a6 6 0 1 1-8.5 0C12.6 51 15 49 15 46v-3c0-7.2-8-13.4-10.3-15.7A16 16 0 0 1 16 0a16 16 0 0 1 11.3 27.3z"/><circle cx="16" cy="56" r="1" fill="#5D509E"/></svg>') top center no-repeat;
position: absolute;
box-sizing: border-box;
left: var(--value-percent, 0%);
bottom: 3px;
width: 32px;
height: 62px;
text-align: center;
padding: 8px 3px 0;
margin: 0 0 0 -16px;
filter: drop-shadow(0 1px 3px rgba(0,0,0,0.3));
transform-origin: 50% 90%;
opacity: 0.0001;
transform: scale(.2);
color: #fff;
font: inherit;
text-overflow: clip;
text-shadow: 0 -.5px 0 rgba(0,0,0,0.4);
transition: transform 200ms ease, opacity 200ms ease;
will-change: transform;
pointer-events: none;
overflow: hidden;
}
range-input input:focus + div span {
opacity: 1;
transform: scale(1);
}