lu2
Version:
Simple and flexible UI component library based on native HTML and JavaScript
411 lines (354 loc) • 13.2 kB
JavaScript
/**
* @Tips.js
* @author zhangxinxu
* @version
* @Created: 15-06-25
* @edit: 17-06-19
*/
/* global module */
(function (global, factory) {
if (typeof exports === 'object' && typeof module !== 'undefined') {
global.Follow = require('./Follow');
module.exports = factory();
} else if (typeof define === 'function' && (define.amd || define.cmd)) {
define(factory);
} else {
global.Tips = factory();
}
// eslint-disable-next-line
}((typeof global !== 'undefined') ? global
// eslint-disable-next-line
: ((typeof window !== 'undefined') ? window
: ((typeof self !== 'undefined') ? self : this)), function (require) {
var Follow = (this || self).Follow;
if (typeof require == 'function' && !Follow) {
Follow = require('common/ui/Follow');
} else if (!Follow) {
window.console.error('need Follow.js');
return {};
}
/**
* 黑色tips效果
* @example
* new Tips(element, options)
**/
// 类名变量
var CL = {
add: function () {
return ['ui-tips'].concat([].slice.call(arguments)).join('-');
},
toString: function () {
return 'ui-tips';
}
};
var REVERSE = 'reverse';
/**
* 黑色tips效果
* @param {Object|String} element 需要提示的元素,也可以是元素的选择器
* @param {Object} options 可选参数
*/
var Tips = function (element, options) {
if (!element) {
return this;
}
var defaults = {
// eventType其他值:'click', 'null'
eventType: 'hover',
// align: auto表示自动根据元素上的属性确定定位方式
// 可以使用data-align或者data-position自定义定位
// 如果没有这些属性值,有类名reverse
// 认为是7-5
// 其他场景认为是5-7
// 支持Follow.js中所有position位置
align: 'auto',
content: '',
onShow: function () {},
onHide: function () {}
};
var eleTrigger = element;
// 如果参数是字符串,认为是选择器
if (typeof element == 'string') {
eleTrigger = document.querySelectorAll(element);
if (eleTrigger.length == 0) {
return this;
}
}
// 避免重复初始化
if (eleTrigger.data && eleTrigger.data.tips) {
return eleTrigger.data.tips;
}
if (eleTrigger.length && eleTrigger.forEach) {
eleTrigger.forEach(function (trigger) {
new Tips(trigger, options);
});
return eleTrigger[0].data.tips;
}
var objParams = Object.assign({}, defaults, options || {});
// 暴露参数
this.element = {
trigger: eleTrigger
};
this.callback = {
show: objParams.onShow,
hide: objParams.onHide
};
this.params = {
eventType: objParams.eventType,
align: objParams.align
};
if (objParams.content) {
this.content = objParams.content;
}
// 事件处理
this.events();
// 存储实例对象
if (!eleTrigger.data) {
eleTrigger.data = {};
}
eleTrigger.data.tips = this;
return this;
};
/**
* 定义一个content属性,可以设置与获取tips提示内容
* @param {[type]} )
*/
Object.defineProperty(Tips.prototype, 'content', {
configurable: true,
enumerable: true,
writeable: true,
get: function () {
var eleTrigger = this.element && this.element.trigger;
if (!eleTrigger) {
return '';
}
var strTitle = eleTrigger.getAttribute('title') || eleTrigger.getAttribute('data-title') || '';
// CSS 驱动的tips提示效果是基于data-title
eleTrigger.setAttribute('data-title', strTitle);
// 屏幕阅读无障碍访问支持
eleTrigger.setAttribute('aria-label', strTitle);
// 移除浏览器默认的title,防止交互冲突
eleTrigger.removeAttribute('title');
return strTitle;
},
set: function (value) {
var eleTrigger = this.element && this.element.trigger;
if (eleTrigger) {
eleTrigger.setAttribute('data-title', value);
// 屏幕阅读无障碍访问支持
eleTrigger.setAttribute('aria-label', value);
}
}
});
/**
* 相关的事件处理
* @return {[type]} [description]
*/
Tips.prototype.events = function () {
var eleTrigger = this.element.trigger;
// 非focusable元素使其focusable
if (/^a|input|button|area$/i.test(eleTrigger.tagName) == false) {
eleTrigger.setAttribute('tabindex', '0');
// 更语义
eleTrigger.setAttribute('role', 'tooltip');
}
// 如果是纯CSS定位实现的tips效果
if (eleTrigger.classList.contains(CL)) {
// 如果元素含有title, 需要转换成data-title
this.content;
// outline处理,点击时候提示不跟随
// 键盘无障碍访问的细节处理
eleTrigger.addEventListener('click', function () {
if (!window.isKeyEvent) {
this.blur();
}
}, false);
return;
}
var objParams = this.params;
// hover显示延迟时间
var numDelay = 100;
// 设置定时器对象
var timerTips;
// 如果是不支持hover行为的浏览器,hover变click
var isHover = getComputedStyle(document.documentElement).getPropertyValue('--hoverNone');
if (isHover && objParams.eventType == 'hover') {
objParams.eventType = 'click';
}
// 事件走起
if (objParams.eventType == 'hover') {
// 鼠标进入
eleTrigger.addEventListener('mouseenter', function () {
timerTips = setTimeout(function () {
this.show();
}.bind(this), numDelay);
}.bind(this));
// 鼠标离开
eleTrigger.addEventListener('mouseleave', function () {
clearTimeout(timerTips);
// 隐藏提示
this.hide();
}.bind(this));
// 支持focus的显示与隐藏
// 但是需要是键盘访问触发的focus
eleTrigger.addEventListener('focus', function () {
if (window.isKeyEvent) {
this.show();
}
}.bind(this));
// 元素失焦
eleTrigger.addEventListener('blur', function () {
this.hide();
}.bind(this));
} else if (objParams.eventType == 'click') {
eleTrigger.addEventListener('click', function () {
this.show();
}.bind(this));
// 关闭
document.addEventListener('mouseup', function (event) {
var eleTarget = event.target;
if (this.display == true && eleTrigger.contains(eleTarget) == false && this.element.tips.contains(eleTarget) == false) {
this.hide();
}
}.bind(this));
} else {
// 其他事件类型直接显示
this.show();
}
};
/**
* tip提示显示
* @param {String} content 显示的提示信息
* @return {Object} 返回当前实例对象
*/
Tips.prototype.show = function () {
var strContent = this.content;
if (!strContent) {
return this;
}
// 元素
var eleTrigger = this.element.trigger;
var eleTips = this.element.tips;
// tips图形需要的元素
var eleContent, eleArrow;
if (eleTips) {
eleTips.style.display = 'block';
// 三角元素获取
eleContent = eleTips.querySelector('span');
eleArrow = eleTips.querySelector('i');
// 坐标还原,因为可能有偏移处理
eleArrow.style.left = '';
// 改变显示内容
eleContent.innerHTML = strContent;
} else {
// eleTips元素不存在,则重新创建
eleTips = document.createElement('div');
eleTips.classList.add(CL.add('x'));
// 创建内容元素和三角元素
eleContent = document.createElement('span');
eleContent.classList.add(CL.add('content'));
// 内容
eleContent.innerHTML = strContent;
eleArrow = document.createElement('i');
eleArrow.classList.add(CL.add('arrow'));
// 屏幕阅读无障碍访问描述
if (!eleTrigger.getAttribute('aria-label')) {
// 创建随机id, aria需要
var strRandomId = 'lulu_' + (Math.random() + '').replace('0.', '');
// 设置
eleContent.id = strRandomId;
eleTrigger.setAttribute('aria-labelledby', strRandomId);
}
// append到页面中
eleTips.appendChild(eleContent);
eleTips.appendChild(eleArrow);
document.body.appendChild(eleTips);
}
// 定位
var strPosition = '5-7';
// 定位只有5-7, 7-5, 6-8, 8-6这几种
// 其中5-7是默认,提示在上方
// 7-5是由'reverse'类名或参数决定的
var strAlign = this.params.align;
if (strAlign == 'auto') {
strAlign = eleTrigger.getAttribute('data-align') || eleTrigger.getAttribute('data-position') || 'center';
}
// 关键字参数与位置
if (strAlign == 'center') {
strPosition = '5-7';
if (eleTrigger.classList.contains(REVERSE) || eleTrigger.hasAttribute(REVERSE)) {
strPosition = '7-5';
}
} else if (strAlign == 'left') {
strPosition = '1-4';
if (eleTrigger.classList.contains(REVERSE) || eleTrigger.hasAttribute(REVERSE)) {
strPosition = '4-1';
}
} else if (strAlign == 'right') {
strPosition = '2-3';
if (eleTrigger.classList.contains(REVERSE) || eleTrigger.hasAttribute(REVERSE)) {
strPosition = '3-2';
}
} else if (/^\d-\d$/.test(strAlign)) {
strPosition = strAlign;
}
new Follow(eleTrigger, eleTips, {
// trigger-target
position: strPosition,
// 边界溢出不自动修正
edgeAdjust: false
});
// 三角的重定位
var strPositionTips = eleTips.getAttribute('data-align');
if (strPositionTips && strPositionTips.split('-').reduce(function (accumulator, currentValue) {
return accumulator * 1 + currentValue * 1;
}) == 5) {
var numHalfOffsetWidth = eleTrigger.getBoundingClientRect().width / 2 - eleArrow.getBoundingClientRect().width / 2;
// 三角偏移处理
if (strPositionTips == '1-4' || strPositionTips == '4-1') {
eleArrow.style.left = numHalfOffsetWidth + 'px';
} else {
eleArrow.style.left = 'calc(100% - ' + (eleArrow.getBoundingClientRect().width + numHalfOffsetWidth) + 'px)';
}
}
// 显示的回调
this.callback.show.call(this, eleTrigger, eleTips);
this.element.tips = eleTips;
this.display = true;
return this;
};
/**
* tip提示隐藏
* @return {Object} 返回当前实例对象
*/
Tips.prototype.hide = function () {
var eleTips = this.element.tips;
if (eleTips) {
eleTips.style.display = 'none';
// 移除回调
this.callback.hide.call(this, this.element.trigger, eleTips);
}
this.display = false;
return this;
};
var strSelector = '.' + CL + ', .jsTips,[is-tips]';
// 自动初始化.ui-tips和.jsTips类名元素
window.addEventListener('DOMContentLoaded', function () {
new Tips(strSelector);
});
// 全局委托
document.addEventListener('mouseover', function (event) {
var eleTrigger = event.target;
if (!eleTrigger || !eleTrigger.closest) {
return;
}
eleTrigger = eleTrigger.closest(strSelector);
if (eleTrigger && (!eleTrigger.data || !eleTrigger.data.tips)) {
new Tips(eleTrigger);
var objTips = eleTrigger.data.tips;
if (objTips && (eleTrigger.classList.contains('jsTips') || eleTrigger.hasAttribute('is-tips'))) {
objTips.show();
}
}
});
return Tips;
}));