实现一个步骤提示插件

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
    }
})()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值