mirror of
https://github.com/GoogleChromeLabs/squoosh.git
synced 2025-11-13 01:07:18 +00:00
<range-input> (#171)
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { h, Component } from 'preact';
|
||||
import { bind } from '../../lib/util';
|
||||
import '../../components/output/custom-els/RangeInput';
|
||||
|
||||
interface EncodeOptions {
|
||||
quality: number;
|
||||
@@ -35,9 +36,8 @@ export default function qualityOption(opts: QualityOptionArg = {}) {
|
||||
<div>
|
||||
<label>
|
||||
Quality:
|
||||
<input
|
||||
<range-input
|
||||
name="quality"
|
||||
type="range"
|
||||
min={min}
|
||||
max={max}
|
||||
step={step || 'any'}
|
||||
|
||||
@@ -50,9 +50,8 @@ export default class QuantizerOptions extends Component<Props, State> {
|
||||
</label>
|
||||
<label style={{ display: options.zx ? 'none' : '' }}>
|
||||
Palette Colors:
|
||||
<input
|
||||
<range-input
|
||||
name="maxNumColors"
|
||||
type="range"
|
||||
min="2"
|
||||
max="256"
|
||||
value={'' + options.maxNumColors}
|
||||
@@ -61,9 +60,8 @@ export default class QuantizerOptions extends Component<Props, State> {
|
||||
</label>
|
||||
<label>
|
||||
Dithering:
|
||||
<input
|
||||
<range-input
|
||||
name="dither"
|
||||
type="range"
|
||||
min="0"
|
||||
max="1"
|
||||
step="0.01"
|
||||
|
||||
@@ -55,12 +55,12 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
filter_type: inputFieldCheckedAsNumber(form.filter_type),
|
||||
use_sharp_yuv: inputFieldCheckedAsNumber(form.use_sharp_yuv),
|
||||
// .value
|
||||
near_lossless: inputFieldValueAsNumber(form.near_lossless),
|
||||
near_lossless: 100 - inputFieldValueAsNumber(form.near_lossless),
|
||||
alpha_quality: inputFieldValueAsNumber(form.alpha_quality),
|
||||
alpha_filtering: inputFieldValueAsNumber(form.alpha_filtering),
|
||||
sns_strength: inputFieldValueAsNumber(form.sns_strength),
|
||||
filter_strength: inputFieldValueAsNumber(form.filter_strength),
|
||||
filter_sharpness: inputFieldValueAsNumber(form.filter_sharpness),
|
||||
filter_sharpness: 7 - inputFieldValueAsNumber(form.filter_sharpness),
|
||||
pass: inputFieldValueAsNumber(form.pass),
|
||||
preprocessing: inputFieldValueAsNumber(form.preprocessing),
|
||||
segments: inputFieldValueAsNumber(form.segments),
|
||||
@@ -74,9 +74,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
<div>
|
||||
<label>
|
||||
Effort:
|
||||
<input
|
||||
<range-input
|
||||
name="lossless_preset"
|
||||
type="range"
|
||||
min="0"
|
||||
max="9"
|
||||
value={'' + determineLosslessQuality(options.quality)}
|
||||
@@ -85,13 +84,11 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Slight loss:
|
||||
<input
|
||||
class={styles.flipRange}
|
||||
<range-input
|
||||
name="near_lossless"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={'' + options.near_lossless}
|
||||
value={'' + (100 - options.near_lossless)}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
@@ -119,9 +116,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
<div>
|
||||
<label>
|
||||
Effort:
|
||||
<input
|
||||
<range-input
|
||||
name="method_input"
|
||||
type="range"
|
||||
min="0"
|
||||
max="6"
|
||||
value={'' + options.method}
|
||||
@@ -130,9 +126,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Quality:
|
||||
<input
|
||||
<range-input
|
||||
name="quality"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.01"
|
||||
@@ -151,9 +146,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Alpha quality:
|
||||
<input
|
||||
<range-input
|
||||
name="alpha_quality"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={'' + options.alpha_quality}
|
||||
@@ -162,9 +156,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Alpha filter quality:
|
||||
<input
|
||||
<range-input
|
||||
name="alpha_filtering"
|
||||
type="range"
|
||||
min="0"
|
||||
max="2"
|
||||
value={'' + options.alpha_filtering}
|
||||
@@ -173,9 +166,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Spacial noise shaping:
|
||||
<input
|
||||
<range-input
|
||||
name="sns_strength"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={'' + options.sns_strength}
|
||||
@@ -193,9 +185,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Filter strength:
|
||||
<input
|
||||
<range-input
|
||||
name="filter_strength"
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
disabled={!!options.autofilter}
|
||||
@@ -214,13 +205,11 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Filter sharpness:
|
||||
<input
|
||||
class={styles.flipRange}
|
||||
<range-input
|
||||
name="filter_sharpness"
|
||||
type="range"
|
||||
min="0"
|
||||
max="7"
|
||||
value={'' + options.filter_sharpness}
|
||||
value={'' + (7 - options.filter_sharpness)}
|
||||
onChange={this.onChange}
|
||||
/>
|
||||
</label>
|
||||
@@ -235,9 +224,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Passes:
|
||||
<input
|
||||
<range-input
|
||||
name="pass"
|
||||
type="range"
|
||||
min="1"
|
||||
max="10"
|
||||
value={'' + options.pass}
|
||||
@@ -258,9 +246,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Segments:
|
||||
<input
|
||||
<range-input
|
||||
name="segments"
|
||||
type="range"
|
||||
min="1"
|
||||
max="4"
|
||||
value={'' + options.segments}
|
||||
@@ -269,9 +256,8 @@ export default class WebPEncoderOptions extends Component<Props, {}> {
|
||||
</label>
|
||||
<label>
|
||||
Partitions:
|
||||
<input
|
||||
<range-input
|
||||
name="partitions"
|
||||
type="range"
|
||||
min="0"
|
||||
max="3"
|
||||
value={'' + options.partitions}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
.flip-range {
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
.hide {
|
||||
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