lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
338 lines (279 loc) • 10.2 kB
JavaScript
/**
* @Range.js
* @author xboxyan
* @version
* @created: 20-04-30
* @edited: 22-02-28 by zhangxinxu
*/
/**/(async () => {
/**/ if (!CSS.supports('overflow-anchor:auto') || !CSS.supports('offset:none')) {
/**/ await import('../safari-polyfill.js');
/**/ }
class UiRange extends HTMLInputElement {
static get observedAttributes () {
return ['max', 'min', 'step', 'disabled'].concat(UiRange.observedUIAttributes);
}
static get observedUIAttributes () {
return ['color', 'width', 'height', 'size'];
}
constructor () {
super();
this.originTips = this.getAttribute('tips');
if (!this.element) {
this.element = {};
}
}
get defaultrange () {
return this.getAttribute('range') || `${this.getAttribute('from') || this.min || 0}, ${this.getAttribute('to') || this.max || 100}`;
}
set multiple (value) {
return this.toggleAttribute('multiple', value);
}
get multiple () {
return this.getAttribute('multiple') !== null;
}
get from () {
if (this.element.brother) {
return Math.min(this.value, this.element.brother.value);
}
return '';
}
get to () {
if (this.element.brother) {
return Math.max(this.value, this.element.brother.value);
}
return '';
}
get range () {
if (this.multiple) {
return this.from + ',' + this.to;
}
return '';
}
get isFrom () {
// 是否为起始range
if (this.element.brother) {
return this.value - this.element.brother.value < 0;
}
return false;
}
set from (v) {
if (this.element.brother) {
if (this.isFrom) {
this.value = v;
} else {
this.element.brother.value = v;
}
}
}
set to (v) {
if (this.element.brother) {
if (!this.isFrom) {
this.value = v;
} else {
this.element.brother.value = v;
}
}
}
set range (v) {
if (this.multiple) {
const [from, to] = v.split(',');
this.to = to;
this.from = from;
}
}
set tips (value) {
if (!value) {
this.removeAttribute('tips');
return;
}
if (!this.originTips) {
this.originTips = value;
}
this.setAttribute('tips', value);
}
get tips () {
const originTips = this.originTips;
if (!originTips) {
return null;
}
// 值
return `${new Function(...['value'], `return \`${originTips}\`;`)(...[this.value])}`;
}
get color () {
return this.getAttribute('color');
}
set color (val) {
this.setAttribute('color', val);
}
get height () {
let height = this.getAttribute('height');
if (!isNaN(height) && !isNaN(parseFloat(height))) {
height = height + 'px';
}
return height;
}
set height (val) {
this.setAttribute('height', val);
}
get size () {
let size = this.getAttribute('size');
if (!isNaN(size) && !isNaN(parseFloat(size))) {
size = size + 'px';
}
return size;
}
set size (val) {
this.setAttribute('size', val);
}
get width () {
let width = this.getAttribute('width');
if (!isNaN(width) && !isNaN(parseFloat(width))) {
width = width + 'px';
}
return width;
}
set width (val) {
this.setAttribute('width', val);
}
setMultiple () {
let eleBrother = this.element.brother;
// 按钮的尺寸和位置
let objBoundRange = this.getBoundingClientRect();
// 尺寸设置
eleBrother.style.width = objBoundRange.width + 'px';
eleBrother.style.height = objBoundRange.height + 'px';
// 偏移对比
let objBoundTrigger = eleBrother.getBoundingClientRect();
let objStyleTrigger = getComputedStyle(eleBrother);
// 目前的偏移
let numOffsetX = objStyleTrigger.getPropertyValue('--ui-offset-x') || 0;
let numOffsetY = objStyleTrigger.getPropertyValue('--ui-offset-y') || 0;
// 偏移误差
let numDistanceX = objBoundRange.left - objBoundTrigger.left;
let numDistanceY = objBoundRange.top - objBoundTrigger.top;
console.log(objBoundRange.left, objBoundTrigger.left);
// 设置新的偏移值
eleBrother.style.setProperty('--ui-offset-x', Number(numOffsetX) + numDistanceX);
eleBrother.style.setProperty('--ui-offset-y', Number(numOffsetY) + numDistanceY);
}
connectedCallback () {
// 一些事件
this.addEventListener('input', this.renderPosition);
this.addEventListener('touchstart', this.stopPropagation);
let eleBrother = this.element.brother;
// 使组件 [is="ui-range"] 一直存在
if (this.getAttribute('is') === null) {
this.setAttribute('is', 'ui-range');
}
// 区间选择
if (this.multiple && !eleBrother && this.getAttribute('rel') !== 'brother') {
eleBrother = this.cloneNode(false);
eleBrother.removeAttribute('id');
eleBrother.originTips = this.originTips;
eleBrother.element = {
brother: this
};
this.element.brother = eleBrother;
// 载入到页面中
this.before(eleBrother);
// 初始化一个前后关系
eleBrother.setAttribute('rel', 'brother');
// 范围
this.range = this.defaultrange;
// 尺寸观察
// 观察尺寸变化
let objResizeObserver = new ResizeObserver(() => {
// 尺寸和偏移处理
this.setMultiple();
});
// 观察文本域元素
objResizeObserver.observe(this);
}
// 全局事件
this.dispatchEvent(new CustomEvent('connected', {
detail: {
type: 'ui-range'
}
}));
this.isConnectedCallback = true;
this.render();
if (eleBrother && eleBrother.getAttribute('rel') == 'brother') {
// 默认先计算下尺寸和位置
this.setMultiple();
}
this.dispatchEvent(new CustomEvent('DOMContentLoaded'));
}
disconnectedCallback () {
this.removeEventListener('input', this.renderPosition);
this.removeEventListener('touchstart', this.stopPropagation);
if (this.element.brother && !this.exchange) {
this.element.brother.remove();
}
}
stopPropagation (ev) {
ev.stopPropagation();
}
attributeChangedCallback (name, oldValue, newValue) {
if (oldValue !== newValue) {
if (name === 'disabled' && this.element.brother) {
this.element.brother.disabled = newValue !== null;
} else if (['color', 'width', 'height'].includes(name)) {
this.renderAttribute(name);
} else {
this.renderPosition();
}
}
}
renderAttribute (name, keep) {
if (this[name] || typeof this[name] == 'string') {
this.style.setProperty('--ui-range-' + name, this[name]);
// 关键字值不处理
if (name == 'size' && /^[a-z]+$/i.test(String(this[name]))) {
this.style.removeProperty('--ui-range-' + name);
}
} else if (!keep) {
this.style.removeProperty('--ui-range-' + name);
}
}
renderPosition () {
const max = this.max || 100;
const min = this.min || 0;
this.style.setProperty('--percent', (this.value - min) / (max - min));
this.tips = this.tips;
this.style.setProperty('--from', this.from);
this.style.setProperty('--to', this.to);
const eleBrother = this.element.brother;
if (eleBrother) {
eleBrother.style.setProperty('--from', this.from);
eleBrother.style.setProperty('--to', this.to);
}
}
render () {
this.renderPosition();
UiRange.observedUIAttributes.forEach(attr => {
this.renderAttribute(attr, true);
});
}
addEventListener (...par) {
document.addEventListener.apply(this, par);
if (this.element.brother) {
document.addEventListener.apply(this.element.brother, par);
}
}
}
const props = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value');
Object.defineProperty(UiRange.prototype, 'value', {
...props,
set (v) {
props.set.call(this, v);
// 重新渲染
this.renderPosition();
}
});
if (!customElements.get('ui-range')) {
customElements.define('ui-range', UiRange, {
extends: 'input'
});
}
/**/})();