Quill默認(rèn)工具欄沒有提示信息宦赠,在playground有Tooltip的實(shí)現(xiàn)例子胸嘴,是基于BootStrap的Tooltip實(shí)現(xiàn)的券犁,但我們也可以用較少代碼埠对,手寫一個(gè)Tooltip模塊络断。
CSS實(shí)現(xiàn)
工具欄上的圖標(biāo)樣式改為相對(duì)定位,再給每個(gè)圖標(biāo)加上屬性tooltip鸠窗,[tooltip]相對(duì)圖標(biāo)定位妓羊,再用偽元素編寫小箭頭和內(nèi)容,就可以實(shí)現(xiàn)提示信息的效果稍计。
index.js
import Quill from 'quill'
import {tooltips} from './tooltip-list.js'
class Tooltip {
constructor (quill) {
this.quill = quill
this.toolbar = quill.getModule('toolbar')
this.buttons = null
this.selectors = null
let toolbarElement = this.toolbar.container
if (toolbarElement) {
this.buttons = toolbarElement.querySelectorAll('button')
this.selectors = toolbarElement.querySelectorAll('.ql-picker')
for (let el of this.buttons) {
this.setTooltip(el)
}
for (let el of this.selectors) {
this.setTooltip(el)
}
}
}
setTooltip (el) {
let format = [].find.call(el.classList, (className) => {
return className.indexOf('ql-') === 0
}).replace('ql-', '')
let tip = ''
if (tooltips[format]) {
let tool = tooltips[format]
if (typeof tool === 'string') {
tip = tool
} else {
let value = el.value || ''
if (value != null && tool[value]) {
tip = tool[value]
}
}
}
Object.assign(el.style, {
'position': 'relative'
})
el.setAttribute('tooltip', tip)
}
}
Quill.register('modules/tooltip', Tooltip)
tooltip.css (參考pure-css-tooltips)
.ql-toolbar [tooltip]{
position:relative;
}
.ql-toolbar [tooltip]::before{
content: "";
position: absolute;
top:-4px;
left:50%;
transform: translateX(-50%);
border-width: 4px 6px 0 6px;
border-style: solid;
border-color: rgba(0,0,0,0.7) transparent transparent transparent;
z-index: 99;
opacity:0;
}
.ql-toolbar [tooltip-position='left']::before{
left:0%;
top:50%;
margin-left:-12px;
transform:translatey(-50%) rotate(-90deg)
}
.ql-toolbar [tooltip-position='top']::before{
left:50%;
}
.ql-toolbar [tooltip-position='bottom']::before{
top:100%;
margin-top:8px;
transform: translateX(-50%) translatey(-100%) rotate(-180deg)
}
.ql-toolbar [tooltip-position='right']::before{
left:100%;
top:50%;
margin-left:1px;
transform:translatey(-50%) rotate(90deg)
}
.ql-toolbar [tooltip]::after {
content: attr(tooltip);
position: absolute;
left:50%;
top:-4px;
transform: translateX(-50%) translateY(-100%);
background: rgba(0,0,0,0.7);
text-align: center;
color: #fff;
padding:4px 2px;
font-size: 12px;
min-width: 70px;
border-radius: 5px;
pointer-events: none;
padding: 4px 4px;
z-index:99;
opacity:0;
}
.ql-toolbar [tooltip-position='left']::after{
left:0%;
top:50%;
margin-left:-8px;
transform: translateX(-100%) translateY(-50%);
}
.ql-toolbar [tooltip-position='top']::after{
left:50%;
}
.ql-toolbar [tooltip-position='bottom']::after{
top:100%;
margin-top:8px;
transform: translateX(-50%) translateY(0%);
}
.ql-toolbar [tooltip-position='right']::after{
left:100%;
top:50%;
margin-left:8px;
transform: translateX(0%) translateY(-50%);
}
.ql-toolbar [tooltip]:hover::after,.ql-toolbar [tooltip]:hover::before {
opacity:1
}
.quill-tooltip::before{
content: "";
position: absolute;
bottom:-4px;
left:50%;
transform: translateX(-50%);
border-width: 4px 6px 0 6px;
border-style: solid;
border-color: rgba(0,0,0,0.7) transparent transparent transparent;
z-index: 99;
}
JS實(shí)現(xiàn)
JS實(shí)現(xiàn)較為麻煩躁绸,需要?jiǎng)?chuàng)建tooltip元素,append到body上臣嚣,監(jiān)聽mouseenter和mouseleave净刮,顯示或隱藏tooltip。但因?yàn)槭窍鄬?duì)body定位硅则,這樣的tooltip避免了被頁面上其他元素遮擋的問題淹父。
另外,滾動(dòng)時(shí)會(huì)出現(xiàn)tooltip跟隨鼠標(biāo)滾動(dòng)的情況怎虫,這時(shí)需要獲取工具欄所在的可滾動(dòng)父元素暑认。在其滾動(dòng)時(shí)困介,隱藏tooltip。
import Quill from 'quill'
import {tooltips} from './tooltip-list.js'
import {getScrollParent} from 'utils/scrollInfo.js'
const tooltipStyles = {
minWidth: '70px',
position: 'absolute',
padding: '4px 8px',
textAlign: 'center',
backgroundColor: 'rgba(0,0,0,0.7)',
color: '#fff',
cursor: 'default',
borderRadius: '4px',
fontSize: '12px',
top: '-9999px',
visibility: 'hidden'
}
class Tooltip {
constructor (quill) {
this.quill = quill
this.toolbar = quill.getModule('toolbar')
this.buttons = null
this.selectors = null
this.tip = null
this.timeout = null
this.mouseenterHandler = null
this.mouseleaveHandler = null
let toolbarElement = this.toolbar.container
if (toolbarElement) {
// 添加處理事件
this.buttons = toolbarElement.querySelectorAll('button')
this.selectors = toolbarElement.querySelectorAll('.ql-picker')
for (let el of this.buttons) {
this.addHandler(el)
}
for (let el of this.selectors) {
this.addHandler(el)
}
// 創(chuàng)建tooltip
this.createTooltip()
// 滾動(dòng)元素增加handler
this.scrollElm = getScrollParent(toolbarElement)
this.scrollElm.addEventListener('scroll', this.mouseleaveHandler)
}
}
createTooltip () {
this.tip = document.createElement('div')
this.tip.classList.add('quill-tooltip')
Object.assign(this.tip.style, tooltipStyles)
document.body.appendChild(this.tip)
}
addHandler (el) {
this.mouseenterHandler = () => {
this.timeout = setTimeout(() => {
this.showTooltip(el)
}, 100)
}
this.mouseleaveHandler = () => {
if (this.timeout) clearTimeout(this.timeout)
this.hideTooltip()
}
el.addEventListener('mouseenter', this.mouseenterHandler)
el.addEventListener('mouseleave', this.mouseleaveHandler)
}
showTooltip (el) {
// let format = el.className.replace('ql-', '')
let format = [].find.call(el.classList, (className) => {
return className.indexOf('ql-') === 0
}).replace('ql-', '')
if (tooltips[format]) {
let tool = tooltips[format]
if (typeof tool === 'string') {
this.tip.textContent = tool
} else {
let value = el.value || ''
if (value != null && tool[value]) {
this.tip.textContent = tool[value]
}
}
const elRect = el.getBoundingClientRect()
const tipRect = this.tip.getBoundingClientRect()
const body = document.documentElement || document.body
const bodyRect = {
width: body.scrollWidth,
height: body.scrollHeight,
scrollTop: body.scrollTop,
scrollLeft: body.scrollLeft
}
const offset = 3
Object.assign(this.tip.style, {
'top': elRect.top - elRect.height - offset + bodyRect.scrollTop + 'px',
'left': elRect.left - (tipRect.width - elRect.width) / 2 + bodyRect.scrollLeft + 'px',
'visibility': 'visible'
})
}
}
hideTooltip () {
Object.assign(this.tip.style, {
top: '-9999px',
visibility: 'hidden'
})
}
onDestroy () {
console.warn('ondestroy')
this.destroyTooltip()
if (this.buttons) {
for (let el of this.buttons) {
this.removeHandler(el)
}
}
if (this.selectors) {
for (let el of this.selectors) {
this.removeHandler(el)
}
}
if (this.scrollElm) this.scrollElm.removeEventListener('scroll', this.mouseleaveHandler)
}
destroyTooltip () {
if (this.tip.parentNode) this.tip.parentNode.removeChild(this.tip)
}
removeHandler (el) {
el.removeEventListener('mouseenter', this.mouseenterHandler)
el.removeEventListener('mouseleave', this.mouseleaveHandler)
}
}
Quill.register('modules/tooltip', Tooltip)
獲取滾動(dòng)父元素的代碼如下(來自ElementUI):
export const getScrollParent = function (element) {
var parent = element.parentNode
if (!parent) {
return element
}
if (parent === document) {
if (document.body.scrollTop !== undefined) {
return document.body
} else {
return document.documentElement
}
}
if (
['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow')) !== -1 ||
['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow-x')) !== -1 ||
['scroll', 'auto'].indexOf(getStyleComputedProperty(parent, 'overflow-y')) !== -1
) {
return parent
}
return getScrollParent(element.parentNode)
}
export const getStyleComputedProperty = function (element, property) {
var css = window.getComputedStyle(element, null)
return css[property]
}
Tooltip中的文本
文本通過獲取圖標(biāo)的className來確定蘸际,如果是純字符串座哩,則直接用tooltips對(duì)象查找tooltips[format],如果還有一層粮彤,則根據(jù)其value進(jìn)一步查找根穷。
let format = [].find.call(el.classList, (className) => {
return className.indexOf('ql-') === 0
}).replace('ql-', '')
if (tooltips[format]) {
let tool = tooltips[format]
if (typeof tool === 'string') {
this.tip.textContent = tool
} else {
let value = el.value || ''
if (value != null && tool[value]) {
this.tip.textContent = tool[value]
}
}
tooltips如下:
export const tooltips = {
'align': {
'': '對(duì)齊',
'center': '居中對(duì)齊',
'right': '右對(duì)齊',
'justify': '兩端對(duì)齊'
},
'background': '背景色',
'blockquote': '引用',
'bold': '加粗',
'clean': '清除格式',
'code': '代碼',
'code-block': '代碼段',
'color': '顏色',
'direction': '方向',
'float': {
'center': '居中',
'full': '全浮動(dòng)',
'left': '左浮動(dòng)',
'right': '右浮動(dòng)'
},
'formula': '公式',
'header': {
'': '標(biāo)題',
'1': '標(biāo)題1',
'2': '標(biāo)題2'
},
'italic': '斜體',
'image': '圖片',
'indent': {
'+1': '縮進(jìn)',
'-1': '取消縮進(jìn)'
},
'link': '鏈接',
'list': {
'ordered': '有序列表',
'bullet': '無序列表',
'check': '選擇列表'
},
'script': {
'sub': '下標(biāo)',
'super': '上標(biāo)'
},
'strike': '刪除線',
'underline': '下劃線',
'video': '視頻',
'undo': '撤銷',
'redo': '重做',
'size': '字號(hào)',
'font': '字體',
'divider': '分割線',
'formatBrush': '格式刷',
'emoji': '表情'
}