實現一個步驟提示插件

stepsTips步驟提示

概述

在後臺管理的實際項目中,發現沒有使用說明文檔的話,用戶在有些操作上不清楚怎麼操作流程纔是正確的(也可能是頁面不夠簡明扼要),所以在假期突然想起來做一個步驟提示的插件來在以後項目中應用,參考了部分 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
    }
})()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章