基于原生js单例模式打造的楼层导航,左右tab实时切换。【硬核】

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<style>
    * {
        margin: 0;
        padding: 0;
    }

    .maxbox {
        width: 1000px;
        margin: auto;
    }

    .nav {
        width: 1000px;
        height: 240px;
        background-image: url('https://tse4-mm.cn.bing.net/th/id/OIP-C.q2IL0JNLOrJIxTvSM_yydAHaEI?w=323&h=180&c=7&r=0&o=5&dpr=1.25&pid=1.7');
        background-repeat: no-repeat;
        background-size: 100% 100%;
        margin-top: 20px;
        padding-top: 50px;
        box-sizing: border-box;
    }

    header {
        width: 50%;
        height: 80%;
        margin: auto;
        border-radius: 5px;
        background: rgba(244, 243, 244, 0.18);
        box-shadow: 0 0.3px 0.7px rgba(0, 0, 0, 0.126),
            0 0.9px 1.7px rgba(0, 0, 0, 0.179), 0 1.8px 3.5px rgba(0, 0, 0, 0.224),
            0 3.7px 7.3px rgba(0, 0, 0, 0.277), 0 10px 20px rgba(0, 0, 0, 0.4);
        backdrop-filter: blur(10px);
    }




    .content #dianying {
        background-color: rgb(151, 90, 231);
    }

    .content #yifu {
        background-color: rgb(74, 238, 82);
    }

    .content #shipin {
        background-color: rgb(236, 223, 35);
    }

    .content #dianqi {
        background-color: rgb(230, 46, 236);
    }

    .content #jiaju {
        background-color: rgb(245, 74, 31);
    }

    .content div {
        width: 100%;
        height: 500px;
        margin: 10px auto;
    }

    .scrollTopTo {
        width: 50px;
        height: 120px;
        background-color: rgb(34, 143, 231);
        box-sizing: border-box;
        position: absolute;
        top: 205px;
        right: 59px;
    }

    .list p {
        width: 100%;
        height: 40px;
        line-height: 40px;
        text-align: center;
        background-color: rgb(35, 166, 218);

    }

    .toTop {
        height: 40px;
        line-height: 40px;
        text-align: center;
        background-color: rgb(35, 166, 218);
        font-size: 20px;
    }

    .scrollTopTo p:hover {
        cursor: pointer;
    }

    .toTop:hover {
        cursor: pointer;
    }

    .active {
        background-color: rgb(223, 148, 35) !important;
        color: red;
    }

    footer {
        width: 100%;
        height: 300px;
        background-color: rgb(189, 182, 182);
    }
</style>

<body>
    <div class="maxbox">
        <div class="nav">
            <header>
                <h1>头部</h1>
            </header>
        </div>
        <div class="content">
            <!-- 电影 -->
            <div id="dianying">
                <h1>电影</h1>
            </div>
            <div id="yifu">
                <h1>衣服</h1>
            </div>
            <div id="shipin">
                <h1>食品</h1>
            </div>
            <div id="dianqi">
                <h1>电器</h1>
            </div>
            <div id="jiaju">
                <h1>家具</h1>
            </div>
        </div>
        <div class="scrollTopTo">
            <div class="list"></div>
            <!-- <p>电影</p>
            <p>衣服</p>
            <p>食品</p>
            <p>电器</p>
            <p>家具</p> -->
            <div class="toTop">
                ∧
            </div>
        </div>


    </div>
    <footer>
        <h1>底部版权信息</h1>
    </footer>
</body>
<script>
    // jquery 的offset 方法用来拿到距离顶部的高度的
    function JqOffset(ele) {
        let result = {
            top: 0,
            left: 0
        }
        // 当前为 IE11 以下,直接返回 {top: 0, left: 0}
        if (!ele.getClientRects().length) {
            return result
        }

        // 当前 DOM 节点的 display === 'none' 时,直接返回 {top: 0, left: 0}
        if (window.getComputedStyle(ele)['display'] === 'none') {
            return result
        }

        result = ele.getBoundingClientRect()
        var docElement = ele.ownerDocument.documentElement

        return {
            top: result.top + window.pageYOffset - docElement.clientTop,
            left: result.left + window.pageXOffset - docElement.clientLeft
        }
    }
    // 节流方法
    function throttle(func, wait = 100) { // 节流的处理
        let Timeout
        return function () {
            if (!Timeout) {
                Timeout = setTimeout(function () {
                    func()
                    Timeout = null
                }, wait)
            }
        }
    }
    // 基于单例模式实现模块化开发
    let navigationOptions = (function () {

        let navigation = document.querySelector('.scrollTopTo')
        HTML = document.documentElement
        content = document.querySelector('.content')
        list = document.querySelector('.list')
        maxbox = document.querySelector('.maxbox')
        // 初始化导航条的位置
        const initPosition = function initPosition() {
            // 计算出导航条距离右侧的位置 (HTML.clientWidth - 1000)得出两边的位置 / 2 得出一侧的位置
            let n = ((HTML.clientWidth - 1000) / 2 - 75)
            if (n < 0) {
                navigation.style.display = 'none'
                return
            }
            console.log(n);
            navigation.style.display = 'block'
            navigation.style.right = n + 'px'

            // 随着页面滚动,控制它的定位
            console.log(HTML.scrollTop);
            if (HTML.scrollTop >= 200) {
                console.log('2646555555555555');
                navigation.style.position = 'fixed'
                navigation.style.top = '20px'
                return
            }
            navigation.style.position = 'absolute'
            navigation.style.top = '250px'
        }


        // 构建数映射模型
        let sourceMap = [{
            id: 'dianying',
            text: '电影',
            top: 0,
            active: false
        }, {
            id: 'yifu',
            text: '衣服',
            top: 1,
            active: false
        }, {
            id: 'shipin',
            text: '食品',
            top: 2,
            active: false
        }, {
            id: 'dianqi',
            text: '电器',
            top: 3,
            active: false
        }, {
            id: 'jiaju',
            text: '家具',
            top: 4,
            active: false
        },]
        let isEdit = false

        // 计算每个每个版块的top值
        const computedTop = function computedTop() {
            sourceMap = sourceMap.map(item => {
                let ele = document.querySelector(`#${item.id}`)
                item.top = JqOffset(ele).top
                console.log(item.top, 'wajroawjkldawwa');
                return item
            })
        }

        // 根据对应的数映射模型渲染右侧导航
        const renderList = function renderList() {
            let str = ``
            sourceMap.forEach(item => {
                // 这里标签一定给他闭合
                str += `<p data-id="${item.id}" class="${item.active ? 'active' : ''} ">${item.text}</p>`
            })
            list.innerHTML = str
        }

        const renderContainer = function renderContainer() {
            let frag = document.createDocumentFragment()
            sourceMap.forEach(item => {
                var ele = document.querySelector(`#${item.id}`)
                frag.appendChild(ele)
            })
            content.appendChild(frag)
            frag = null
        }

        // 滚动中哪一个选中
        const computedActive = function computedActive() {
            if (isEdit) { return }
            // 获取滚动的高度
            let top = HTML.scrollTop
            console.log(top);
            // 滚动的时候默认不选中
            sourceMap = sourceMap.map(item => {
                item.active = false
                return item
            })
            if (top >= sourceMap[sourceMap.length - 1].top) { // 选中最后一个值
                sourceMap[sourceMap.length - 1].active = true
            } else if (top >= sourceMap[0].top) {// 说明第一个选中
                for (let i = 0; i < sourceMap.length; i++) {
                    let item = sourceMap[i] // 拿到当前的值
                    next = sourceMap[i + 1] // 拿到下一个值
                    if (top >= item.top && top < next.top) { // 就让当前的状态等于 true
                        item.active = true
                        break
                    }
                }
            }

            renderList()// 根据对应的数映射模型渲染右侧导航

        }
        // 点击右边的nav调到指定位置
        const clickDelegate = function clickDelegate() {// 右侧导航条的事件
            navigation.addEventListener('click', function (e) {
                let target = e.target
                targetTag = target.tagName
                console.log(targetTag);
                targetClass = target.classList
                // 合并事件源
                if (targetTag === "I") {
                    target = target.parentNode
                    targetTag = target.tagName
                    targetClass = target.classList
                }
                // 回到顶部
                if (targetTag == 'DIV' && targetClass.contains('toTop')) {
                    let n = 30
                    const timer = setInterval(() => {
                        if (HTML.scrollTop <= 0) {
                            clearInterval(timer)
                            return
                        }
                        HTML.scrollTop -= n
                    }, 0.1)

                }

                // 这个是右侧的点击事件
                if (targetTag == 'P') {
                    let data_id = target.getAttribute('data-id')
                    console.log(data_id)
                    result = sourceMap.find(item => item.id == data_id)
                    result.active = true

                    if (result) {
                        // 这边要以一个一获取坐标的时候没有取正所以有偏差
                        HTML.scrollTop = result.top + 1
                    }
                }
            })

            renderList()// 根据对应的数映射模型渲染右侧导航
        }




        return {
            init() {
                // 命令业务 控制代码的执行顺序 业务功能
                initPosition()
                computedTop() // 计算每个版块高度
                renderContainer()
                clickDelegate() // 右侧导航条的事件

                // 窗口改变监听 事件
                window.addEventListener('resize', function () {
                    initPosition()
                    computedTop() // 计算每个版块高度
                })
                // 滚动状态频率过快
                window.addEventListener('scroll', throttle(function () {
                    initPosition()
                    computedActive()
                }))
            }
        }
    })()

    window.addEventListener('load', navigationOptions.init)

</script>

</html>

上一篇:vue响应式,数据劫持&发布订阅实现


下一篇:部分JS原生方法手写