mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 09:17:20 +00:00
<range-input> (#171)
This commit is contained in:
@@ -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'}
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
.flip-range {
|
|
||||||
transform: scaleX(-1);
|
|
||||||
}
|
|
||||||
.hide {
|
.hide {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|||||||
115
src/components/Output/custom-els/RangeInput/index.ts
Normal file
115
src/components/Output/custom-els/RangeInput/index.ts
Normal 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);
|
||||||
5
src/components/Output/custom-els/RangeInput/missing-types.d.ts
vendored
Normal file
5
src/components/Output/custom-els/RangeInput/missing-types.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
declare namespace JSX {
|
||||||
|
interface IntrinsicElements {
|
||||||
|
'range-input': HTMLAttributes;
|
||||||
|
}
|
||||||
|
}
|
||||||
109
src/components/Output/custom-els/RangeInput/styles.css
Normal file
109
src/components/Output/custom-els/RangeInput/styles.css
Normal 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);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user