概述
在後臺管理的實際項目中,發現沒有使用說明文檔的話,用戶在有些操作上不清楚怎麼操作流程纔是正確的(也可能是頁面不夠簡明扼要),所以在假期突然想起來做一個步驟提示的插件來在以後項目中應用,參考了部分 jquery.joyride 參數獲取的實現,查看源碼。
2020-3-14 加入“不再提示”按鈕
2020-3-30 綁定step時,觸發destroyAll銷燬
在線示例
源碼
/**
* 步驟提示插架 steps
* Author: chengzan
* UpdateTime: 2020-3-30 15:32
* Version: 1.0.004
* */
const steps = (function() {
// 綁定的元素,元素的子元素對應tips配置和內容
let _stepsBindEl
// callback
let _cb
// 滾動定時器
let timer
// 獲取元素位置和寬高信息
function _getElInfo(el) {
return {
width: el.offsetWidth,
height: el.offsetHeight,
left: getElementLeft(el),
top: getElementTop(el),
}
}
// 獲取元素左偏移
function getElementLeft(element) {
var actualLeft = element.offsetLeft
var current = element.offsetParent
while (current !== null) {
actualLeft += current.offsetLeft
current = current.offsetParent
}
return actualLeft
}
// 獲取元素頂部偏移
function getElementTop(element) {
var actualTop = element.offsetTop
var current = element.offsetParent
while (current !== null) {
actualTop += current.offsetTop
current = current.offsetParent
}
return actualTop
}
// 獲取需要滾動的距離
function scrollToEl(el) {
const innerHeight = window.innerHeight
// steps 的元素信息,寬、高、據頂、距底部位置
let elPositionInfo = _getElInfo(el)
let scrollTop = 0
if (elPositionInfo.top > innerHeight) {
scrollTop = elPositionInfo.top - innerHeight + elPositionInfo.height + 20
}
// 如果元素位置在首屏之外,那麼就滾動到元素位置
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(function() {
let now = document.body.scrollTop || document.documentElement.scrollTop
let speed = (scrollTop - now) / 10
speed = speed < 0 ? Math.ceil(speed) : Math.floor(speed)
scrollToEl(el)
if (now === scrollTop || speed === 0) {
clearTimeout(timer)
}
document.body.scrollTop += speed
document.documentElement.scrollTop += speed
}, 25)
}
// 組裝步驟顯示元素
function initSteps(els) {
for (let i = 0; i < els.length; i++) {
createBox(els[i], i)
}
nextIndex(0, els[0])
document.body.classList.add('body-no-scroll')
}
// 創建steps的盒子
function createBox(el, index) {
const boxInfo = el.dataset
let elPositionInfo
let contentElement = document.getElementById(boxInfo.bindId) || document.getElementsByClassName(boxInfo.bindClass)[0]
if (boxInfo.bindId) {
elPositionInfo = _getElInfo(document.getElementById(boxInfo.bindId))
} else {
elPositionInfo = _getElInfo(document.getElementsByClassName(boxInfo.bindClass)[0])
}
// 創建父級,加入boxShadow只顯示el的內容
const parentEl = document.createElement('div')
parentEl.classList.add('steps-parent', 'steps-hidden')
parentEl.style.left = `${elPositionInfo.left}px`
parentEl.dataset.index = index
// mask 遮罩
const maskEl = document.createElement('div')
maskEl.classList.add('steps-mask', 'steps-hidden')
document.body.appendChild(maskEl)
// 遮擋的內容
const contentShowEl = document.createElement('div')
contentShowEl.classList.add('steps-show-content', 'steps-hidden')
contentShowEl.dataset.index = index
contentShowEl.style.top = `${elPositionInfo.top}px`
contentShowEl.style.left = `${elPositionInfo.left}px`
contentShowEl.style.width = `${elPositionInfo.width}px`
contentShowEl.style.height = `${elPositionInfo.height}px`
document.body.appendChild(contentShowEl)
// 步驟提示內容
const contentEl = document.createElement('div')
contentEl.classList.add('steps-content')
const childrens = el.children
for (let i = 0; i < childrens.length; i++) {
contentEl.appendChild(childrens[i].cloneNode(true))
}
parentEl.appendChild(contentEl)
const buttonGroup = document.createElement('div')
buttonGroup.classList.add('steps-btn-group')
// 加入下一步按鈕
const nextButton = document.createElement('button')
nextButton.classList.add('steps-next')
nextButton.innerText = _stepsBindEl[0].querySelectorAll('li').length === index + 1 ? '關閉' : '下一步'
nextButton.addEventListener('click', () => {
hideSteps(index)
_cb({index: index})
if (index < _stepsBindEl[0].querySelectorAll('li').length - 1) {
nextIndex(index + 1)
} else {
document.body.classList.remove('body-no-scroll')
}
contentElement.classList.remove('steps-show-element')
})
buttonGroup.appendChild(nextButton)
// 加入不再提示按鈕
const noTipsButton = document.createElement('button')
noTipsButton.classList.add('steps-no-tips')
noTipsButton.innerText = '不再提示'
noTipsButton.addEventListener('click', () => {
hideSteps(index)
_cb({
index: index,
noShow: true
})
contentElement.classList.remove('steps-show-element')
document.body.classList.remove('body-no-scroll')
})
buttonGroup.appendChild(noTipsButton)
parentEl.appendChild(buttonGroup)
document.body.appendChild(parentEl)
if (boxInfo.position === 'top') {
parentEl.style.top = `${elPositionInfo.top - parentEl.offsetHeight}px`
parentEl.style.left = `${elPositionInfo.left}px`
} else if (boxInfo.position === 'bottom') {
parentEl.style.top = `${elPositionInfo.top + elPositionInfo.height}px`
parentEl.style.left = `${elPositionInfo.left}px`
} else if (boxInfo.position === 'left') {
parentEl.style.top = `${elPositionInfo.top}px`
parentEl.style.left = `${elPositionInfo.left - parentEl.offsetWidth}px`
} else if (boxInfo.position === 'right') {
parentEl.style.top = `${elPositionInfo.top}px`
parentEl.style.left = `${elPositionInfo.left + elPositionInfo.width}px`
}
parentEl.dataset.position = boxInfo.position || 'top'
}
// 顯示steps
function nextIndex(index) {
const dataset = _stepsBindEl[0].querySelectorAll('li')[index].dataset
const showElement = document.getElementById(dataset.bindId) || document.getElementsByClassName(dataset.bindClass)[0];
[].slice.call(document.getElementsByClassName('steps-show-element')).forEach(e => e.classList.remove('steps-hidden'))
showElement.classList.add('steps-show-element')
if (dataset.bindId) {
scrollToEl(document.getElementById(dataset.bindId))
} else {
scrollToEl(document.getElementsByClassName(dataset.bindClass)[0])
}
document.getElementsByClassName('steps-parent')[index].classList.remove('steps-hidden')
document.getElementsByClassName('steps-mask')[index].classList.remove('steps-hidden')
document.getElementsByClassName('steps-show-content')[index].classList.remove('steps-hidden')
}
// 隱藏steps
function hideSteps(index) {
document.getElementsByClassName('steps-parent')[index].classList.add('steps-hidden')
document.getElementsByClassName('steps-mask')[index].classList.add('steps-hidden')
document.getElementsByClassName('steps-show-content')[index].classList.add('steps-hidden')
}
function destroyAll() {
[].slice.call(document.getElementsByClassName('steps-parent')).forEach(e => e.remove());
[].slice.call(document.getElementsByClassName('steps-mask')).forEach(e => e.remove());
[].slice.call(document.getElementsByClassName('steps-show-content')).forEach(e => e.remove())
document.body.classList.remove('body-no-scroll')
}
return {
bind: function(param, cb, config) {
destroyAll()
_stepsBindEl = document.querySelectorAll(param.el)
_cb = cb
// 首個參數是數組
if (_stepsBindEl.length === 0) {
throw Error('Not find id element')
}
const els = _stepsBindEl[0].querySelectorAll('li')
if (els.length > 0) {
initSteps(els)
} else {
throw Error('Not find li element')
}
return true
},
destroyAll: destroyAll
}
})()