lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
555 lines (490 loc) • 18.6 kB
JavaScript
/**
* @Pagination.js
* @author sunmeiye
* @version
* @Created: 20-06-07
* @edit: 20-06-07
*/
// ui tab custom HTML
class Tab extends HTMLElement {
static get observedAttributes () {
return ['open'];
}
static get defaults () {
return {
eventType: 'click',
history: false,
autoplay: 3000
};
}
constructor (trigger, options) {
super();
// 元素的获取
let eleTrigger = null;
if (typeof trigger == 'string') {
eleTrigger = document.getElementById(trigger);
} else if (typeof trigger == 'object') {
if (trigger.tagName) {
eleTrigger = trigger;
} else if (!options) {
options = trigger;
}
}
options = options || {};
if (eleTrigger) {
// is-tab处理
let strIsTab = eleTrigger.getAttribute('is-tab');
// 必须有data-name值
const strName = eleTrigger.dataset.name;
// 上一项下一项
if (strName && (strIsTab == 'prev' || strIsTab == 'next')) {
eleTrigger.addEventListener('click', () => {
const eleTabGroup = document.querySelectorAll('ui-tab[name="' + strName + '"]');
const indexOpen = [...eleTabGroup].findIndex(eleTab => {
return eleTab.open;
});
if (strIsTab == 'prev') {
(eleTabGroup[indexOpen - 1] || eleTabGroup[eleTabGroup.length - 1]).switch();
} else {
(eleTabGroup[indexOpen + 1] || eleTabGroup[0]).switch();
}
});
// 如果是定时播放,暂停
eleTrigger.addEventListener('mouseenter', () => {
document.querySelectorAll('ui-tab[name="' + strName + '"][autoplay]').forEach(eleTab => {
clearTimeout(eleTab.timer);
})
});
eleTrigger.addEventListener('mouseout', () => {
[...document.querySelectorAll('ui-tab[name="' + strName + '"][autoplay]')].some(eleTab => {
if (eleTab.open) {
eleTab.autoSwitch();
return true;
}
})
});
return;
}
let strTriggerId = eleTrigger.id;
if (!strTriggerId) {
strTriggerId = ('lulu_' + Math.random()).replace('0.', '');
eleTrigger.id = strTriggerId;
// for属性设置
this.htmlFor = strTriggerId;
}
// 参数转移
if (eleTrigger.getAttribute('name')) {
this.name = eleTrigger.getAttribute('name');
}
if (eleTrigger.hasAttribute('open')) {
this.open = true;
}
for (let paramsKey in Tab.defaults) {
if (typeof eleTrigger.dataset[paramsKey.toLowerCase()] != 'undefined') {
this[paramsKey] = eleTrigger.dataset[paramsKey.toLowerCase()];
} else if (typeof options[paramsKey] != 'undefined') {
this[paramsKey] = eleTrigger.dataset[paramsKey.toLowerCase()] = options[paramsKey];
}
}
// target值也可以使用is-tab属性设置
if (strIsTab && !eleTrigger.dataset.target) {
eleTrigger.dataset.target = strIsTab;
}
// 非控件元素的键盘访问支持
if (eleTrigger.tabIndex == -1) {
eleTrigger.setAttribute('tabindex', 0);
}
}
// 参数和元素设置的处理
let objElement = this.element || {};
// trigger和target元素的设置与获取
this.element = new Proxy(objElement, {
get: (target, prop) => {
if (prop == 'target') {
let strIdTarget = this.target;
let eleTarget = null;
let eleTrigger = this.element.trigger;
if (!strIdTarget && eleTrigger) {
strIdTarget = eleTrigger.dataset.target || eleTrigger.getAttribute('href');
if (strIdTarget && /#/.test(strIdTarget)) {
strIdTarget = strIdTarget.split('#')[1];
}
}
// 对应的目标元素获取
if (strIdTarget) {
eleTarget = document.getElementById(strIdTarget);
}
return eleTarget;
}
if (prop == 'trigger') {
return (this.htmlFor && document.getElementById(this.htmlFor)) || this;
}
return target[prop];
},
set: (target, prop, value) => {
if (typeof value == 'string') {
value = document.getElementById(value) || document.querySelector(value);
}
// 只有当value是节点元素时候才赋值
if (value && typeof value.nodeType != 'number') {
return false;
}
// 元素赋值
target[prop] = value;
// target元素设置时候同时需要赋值
if (prop == 'target' && value) {
let eleTarget = value;
let strId = eleTarget.id;
if (!strId) {
strId = ('lulu_' + Math.random()).replace('0.', '');
eleTarget.id = strId;
}
// 如果用户直接使用this.element.target赋值,则需要同步target属性值
if (this.element.trigger == this) {
// 此if判断可以避免死循环
if (this.target != strId) {
this.target = strId;
}
} else if (this.element.trigger) {
this.element.trigger.setAttribute('data-target', strId);
}
}
return true;
}
});
// 参数设置
// 在本组件中,作用较弱
this.setParams(options);
// 载入到页面
if (eleTrigger && !this.parentElement && eleTrigger != this) {
// 使用专门的div包裹,避免暴露过多的细节
let eleHidden = document.querySelector('body > div[hidden="tab"]');
if (!eleHidden) {
eleHidden = document.createElement('div');
eleHidden.setAttribute('hidden', 'tab');
document.body.append(eleHidden);
}
eleHidden.append(this);
}
}
get eventType () {
let strEventType = this.getAttribute('eventtype') || Tab.defaults.eventType;
if (strEventType == 'hover') {
strEventType = 'mouseenter';
}
return strEventType;
}
set eventType (value) {
this.setAttribute('eventtype', value);
}
get history () {
return this.hasAttribute('history') || Tab.defaults.history;
}
set history (value) {
this.toggleAttribute('history', value);
}
get autoplay () {
let strAttrAutoplay = this.getAttribute('autoplay');
if (typeof strAttrAutoplay !== 'string') {
return false;
}
if (/^\d+$/.test(strAttrAutoplay)) {
return strAttrAutoplay * 1;
}
return Tab.defaults.autoplay;
}
set autoplay (value) {
if (!value && value !== '') {
this.removeAttribute('autoplay');
} else {
this.setAttribute('autoplay', value);
}
}
get name () {
return this.getAttribute('name');
}
set name (value) {
this.setAttribute('name', value);
}
get htmlFor () {
return this.getAttribute('for');
}
set htmlFor (v) {
// 设置属性
this.setAttribute('for', v);
}
// 目标元素的设置与获取
get target () {
return this.getAttribute('target');
}
set target (value) {
this.setAttribute('target', value);
}
get open () {
return this.hasAttribute('open');
}
set open (value) {
this.toggleAttribute('open', value);
}
// 参数批量设置
setParams (options) {
this.params = this.params || {};
options = options || {};
Object.assign(this.params, options);
}
switch () {
let strName = this.name;
let eleTabGroup = [];
// 对应选项卡显示的处理
if (strName) {
eleTabGroup = document.querySelectorAll('ui-tab[name="' + strName + '"]');
if (!this.open) {
eleTabGroup.forEach(tab => {
if (tab.open) {
tab.open = false;
}
});
this.open = true;
}
} else {
this.open = !this.open;
}
const location = window.location;
// 历史记录的处理
if (this.history == true && strName && /tab\d{10,16}/.test(strName) == false) {
if (!this.element.target) {
return;
}
let strId = this.element.target.id;
// url地址查询键值对替换
const objURLParams = new URLSearchParams(location.search);
// 改变查询键值,有则替换,无则新增
objURLParams.set(strName, strId);
// hash优化,去除重复的hash
let strHash = location.hash;
if (strId == strHash) {
location.hash = strHash = '';
}
// 改变当前URL
window.history.replaceState(null, document.title, location.href.split('?')[0] + '?' + objURLParams.toString() + strHash);
}
}
// 自动切换
autoSwitch () {
// 自动播放
let numTimeAutoplay = this.autoplay;
let strName = this.name;
if (numTimeAutoplay && strName) {
let eleTabGroup = document.querySelectorAll('ui-tab[name="' + strName + '"]');
clearTimeout(this.timer);
if (numTimeAutoplay && strName && eleTabGroup.length > 1) {
let indexTab = [].slice.call(eleTabGroup).findIndex(tab => {
return tab == this;
});
indexTab++;
if (indexTab >= eleTabGroup.length) {
indexTab = 0;
}
this.timer = setTimeout(() => {
eleTabGroup[indexTab].switch();
// 自动播放
eleTabGroup[indexTab].autoSwitch();
}, numTimeAutoplay);
}
}
}
// 初始化tab事件
events () {
// hover事件的键盘访问支持
if (this.eventType == 'mouseover' || this.eventType == 'mouseenter') {
// 增加延时判断,避免误经过产生的不好体验
this.element.trigger.addEventListener(this.eventType, () => {
Tab.hoverTimer = setTimeout(() => {
this.switch();
}, 150);
}, false);
// 如果快速移出,则不执行切换
this.element.trigger.addEventListener(this.eventType.replace('over', 'out').replace('enter', 'leave'), () => {
clearTimeout(Tab.hoverTimer);
});
}
// 无论什么事件类型,都需要click事件兜底
this.element.trigger.addEventListener('click', (event) => {
if (/^(:?javas|#)/.test(event.target.getAttribute('href'))) {
event.preventDefault();
}
this.switch();
}, false);
// 如果定时播放,鼠标经过暂停
if (this.autoplay && this.name) {
[this.element.trigger, this.element.target].forEach((ele) => {
ele.addEventListener('mouseenter', () => {
clearTimeout(this.timer);
});
ele.addEventListener('mouseleave', () => {
this.autoSwitch();
});
});
// 当前open选项卡开始准备自动播放
let eleFirstOpenTab = document.querySelector('ui-tab[name="' + this.name + '"][open]');
if (eleFirstOpenTab) {
eleFirstOpenTab.autoSwitch();
}
}
}
// ui-tab元素在页面出现的时候
connectedCallback () {
if (!this.closest('a, button') && !this.querySelector('a, button')) {
this.setAttribute('tabindex', '0');
}
let eleTarget = this.element.target;
let eleTrigger = this.element.trigger;
if (eleTrigger) {
eleTrigger.setAttribute('role', 'tab');
}
if (eleTarget) {
eleTarget.setAttribute('role', 'tabpanel');
}
// 事件
this.events();
// URL查询看看能不能获取到记录的选项卡状态信息
let objURLParams = new URLSearchParams(window.location.search);
objURLParams.forEach((value, key) => {
if (eleTrigger && eleTarget && this.name == key && eleTarget.id == value && !eleTrigger.hasAttribute('open')) {
eleTrigger.click();
}
});
// 全局事件
this.dispatchEvent(new CustomEvent('connected', {
detail: {
type: 'ui-tab'
}
}));
this.isConnectedCallback = true;
this.dispatchEvent(new CustomEvent('DOMContentLoaded'));
// is-tab等类型初始化完毕标志事件
if (eleTrigger != this) {
eleTrigger.dispatchEvent(new CustomEvent('DOMContentLoaded'));
if (eleTrigger.hasAttribute('is-tab')) {
eleTrigger.dispatchEvent(new CustomEvent('connected', {
detail: {
type: 'ui-tab'
}
}));
// 设置定义完毕标志量
eleTrigger.setAttribute('defined', '');
}
}
}
/**
* open属性变化时候的变化
* @param {*} name
* @param {*} newValue
* @param {*} oldValue
*/
attributeChangedCallback (name, newValue, oldValue) {
if (this.element && name === 'open' && typeof newValue != typeof oldValue) {
const elePanel = this.element.target;
if (!elePanel) {
return;
}
if (this.open) {
elePanel.classList.add('active');
this.setAttribute('aria-selected', 'true');
this.dispatchEvent(new CustomEvent('show', {
detail: {
type: 'ui-tab'
}
}));
} else {
elePanel.classList.remove('active');
this.setAttribute('aria-selected', 'false');
this.dispatchEvent(new CustomEvent('hide', {
detail: {
type: 'ui-tab'
}
}));
}
// is-tab等普通元素的open属性变化
let eleTrigger = this.element.trigger;
if (eleTrigger && eleTrigger != this) {
eleTrigger.toggleAttribute('open', this.open);
}
// 内置选项卡划来划去效果
if (eleTrigger && this.name && /ui-tab/.test(eleTrigger.className)) {
eleTrigger.parentElement.style.setProperty('--ui-tab-width', eleTrigger.clientWidth);
eleTrigger.parentElement.style.setProperty('--ui-tab-left', eleTrigger.offsetLeft);
}
// 无论选项卡切换还是隐藏,都会触发switch事件
this.dispatchEvent(new CustomEvent('switch'));
}
}
}
// 扩展HTML元素tab的方法
NodeList.prototype.tab = function (options = {}) {
const eleTabs = this;
let strName = options.name || '';
for (var eleTab of eleTabs) {
if (!strName && eleTab.getAttribute('name')) {
strName = eleTab.getAttribute('name');
}
}
if (!strName) {
strName = ('tab' + Math.random()).replace('0.', '');
}
for (let eleTab of eleTabs) {
eleTab.setAttribute('name', strName);
eleTab['ui-tab'] = new Tab(eleTab, options);
}
};
// 定义ui-tab
if (!customElements.get('ui-tab')) {
customElements.define('ui-tab', Tab);
}
window.Tab = Tab;
/**
* 自动化
*/
(function () {
/**
*
*/
function funAutoInitAndWatching () {
document.querySelectorAll('[is-tab]').forEach(function (eleTab) {
if (!eleTab['ui-tab']) {
eleTab['ui-tab'] = new Tab(eleTab);
}
});
var observerTab = new MutationObserver(function (mutationsList) {
mutationsList.forEach(function (mutation) {
mutation.addedNodes.forEach(function (eleAdd) {
if (!eleAdd.tagName) {
return;
}
if (eleAdd.hasAttribute('is-tab')) {
if (!eleAdd['ui-tab']) {
eleAdd['ui-tab'] = new Tab(eleAdd);
}
} else {
eleAdd.querySelectorAll('[is-tab]').forEach(function (eleTab) {
if (!eleTab['ui-tab']) {
eleTab['ui-tab'] = new Tab(eleTab);
}
});
}
});
});
});
observerTab.observe(document.body, {
childList: true,
subtree: true
});
}
if (document.readyState != 'loading') {
funAutoInitAndWatching();
} else {
window.addEventListener('DOMContentLoaded', funAutoInitAndWatching);
}
})();
// 只有type=module的时候可以用,否则会报错
export default Tab;