Javascript 多阶段渲染场景中的VueJS/JS DOM Watch/Observer

Javascript 多阶段渲染场景中的VueJS/JS DOM Watch/Observer,javascript,vue.js,Javascript,Vue.js,场景: 我正在开发一个Vue scroll组件,它环绕动态数量的HTML部分,然后动态构建垂直页面导航,允许用户滚动或跳转到Croll上的页面位置 详细信息: <template> <div v-scroll="handleScroll"> <nav class="nav__wrapper" id="navbar-example"> <ul class="nav"> &

场景:

我正在开发一个Vue scroll组件,它环绕动态数量的HTML部分,然后动态构建垂直页面导航,允许用户滚动或跳转到Croll上的页面位置

详细信息:

<template>
    <div v-scroll="handleScroll">
        <nav class="nav__wrapper" id="navbar-example">
            <ul class="nav">
                <li role="presentation"
                    :id="sideNavPrefix + '-' + (index + 1)"
                    v-for="(item, key,index) in page.sections">
                    <a :href="'#' + getAttribute(item,'id')">
                    <p class="nav__counter" v-text="('0' + (index + 1))"></p>
                        <h3 class="nav__title" v-text="getAttribute(item,'data-title')"></h3>
                        <p class="nav__body" v-text="getAttribute(item,'data-body')"></p>
                    </a>
                </li>
            </ul>
        </nav>
        <slot></slot>
    </div>
</template>

<script>
    import ScrollPageService from '../services/ScrollPageService.js';

    const _S = "section", _N = "sidenavs";

    export default {
        name: "ScrollSection",
        props: {
            nodeId: {
                type: String,
                required: true
            },
            sideNavActive: {
                type: Boolean,
                default: true,
                required: false
            },
            sideNavPrefix: {
                type: String,
                default: "js-side-nav",
                required: false
            },
            sideNavClass: {
                type: String,
                default: "active",
                required: false
            },
            sectionClass: {
                type: String,
                default: "inview",
                required: false
            }
        },
        directives: {
            scroll: {
                inserted: function (el, binding, vnode) {
                    let f = function(evt) {
                        if (binding.value(evt, el)) {
                            window.removeEventListener('scroll', f);
                        }
                    };
                    window.addEventListener('scroll', f);
                }
            },
        },
        data: function () {
            return {
                scrollService: {},
                page: {
                    sections: {},
                    sidenavs: {}
                }
            }
        },
        methods: {
            getAttribute: function(element, key) {
                return element.getAttribute(key);
            },
            updateViewPort: function() {
                if (this.scrollService.isInCurrent(window.scrollY)) return;

                [this.page.sections, this.page.sidenavs] = this.scrollService.updateNodeList(window.scrollY);

            },
            handleScroll: function(evt, el) {
                if ( !(this.isScrollInstance()) ) {
                    return this.$nextTick(this.inViewportInit);
                }

                this.updateViewPort();
            },
            getNodeList: function(key) {
                this.page[key] = this.scrollService.getNodeList(key);
            },
            isScrollInstance: function() {
                return this.scrollService instanceof ScrollPageService;
            },
            sideNavInit: function() {
                if (this.isScrollInstance() && this.scrollService.navInit(this.sideNavPrefix, this.sideNavClass)) this.getNodeList(_N);
            },
            inViewportInit: function() {
                if (!(this.isScrollInstance()) && ((this.scrollService = new ScrollPageService(this.nodeId, this.sectionClass)) instanceof ScrollPageService)) this.getNodeList(_S);
            },
            isNodeList: function(nodes) {
                return NodeList.prototype.isPrototypeOf(nodes);
            },
        },
        watch: {
            'page.sections':  {
                handler(nodeList, oldNodeList){
                    if (this.isNodeList(nodeList) && _.size(nodeList) && this.sideNavActive) {
                        return this.$nextTick(this.sideNavInit);
                    }
                },
                deep: true
            },
        },
        mounted() {
            return this.$nextTick(this.inViewportInit);
        },
    }

</script>

a.在我的示例中,我的滚动组件包装了3个部分。所有节id都以
“js page section-{{index}}”开头

b.目标是获取节节点列表(如上),然后根据查询匹配选择器条件中找到的n个节点动态构建垂直页面(nav)导航。因此,三个部分将产生三个页面部分导航项。所有侧面导航都以“js侧面导航-{{index}}>”开始

c.呈现侧导航后,我需要查询所有导航节点,以控制类别、高度、显示、不透明度等,即
document.queryselectoral('*[id^=“js side nav”]”);

编辑

根据一些研究,这里是我的问题的选项。我的问题是3阶段DOM状态管理,即步骤1。读取等于x的所有节点,然后步骤2。基于文档中n个节点构建侧导航滚动,然后步骤3。读取所有导航节点以与文档节点滚动同步:

  • 创建某种类型的事件系统是
    $emit()&&&$on
    。在我看来,这很快就会变得混乱,感觉是一个糟糕的解决方案。我发现自己很快就跳到了$root
  • Vuex
    。但这感觉有点过头了
  • sync
    。可以工作,但实际上这是用于父子属性状态管理的,但这同样需要
    $emit()&&$on
  • 基于Promise
    的服务类。这似乎是正确的解决方案,但坦率地说,管理多个Promise有点麻烦
  • 我尝试使用Vue$ref,但坦率地说,它似乎更适合于管理状态,而不是使用观察者事件方法更好的多阶段DOM操作
  • 似乎有效的解决方案是Vues
    $nextTick()
    。它似乎类似于AngularJS
    $digest
    。本质上它是一个
    .setTimeout()
    。键入approach只是在下一个摘要周期暂停。也就是说,有一种情况下,滴答声不同步所需的时间,所以我构建了一个节流方法。下面是代码更新
  • 使用nextTick()重构手表

    重构的Vue组件

    <template>
        <div v-scroll="handleScroll">
            <nav class="nav__wrapper" id="navbar-example">
                <ul class="nav">
                    <li role="presentation"
                        :id="sideNavPrefix + '-' + (index + 1)"
                        v-for="(item, key,index) in page.sections">
                        <a :href="'#' + getAttribute(item,'id')">
                        <p class="nav__counter" v-text="('0' + (index + 1))"></p>
                            <h3 class="nav__title" v-text="getAttribute(item,'data-title')"></h3>
                            <p class="nav__body" v-text="getAttribute(item,'data-body')"></p>
                        </a>
                    </li>
                </ul>
            </nav>
            <slot></slot>
        </div>
    </template>
    
    <script>
        import ScrollPageService from '../services/ScrollPageService.js';
    
        const _S = "section", _N = "sidenavs";
    
        export default {
            name: "ScrollSection",
            props: {
                nodeId: {
                    type: String,
                    required: true
                },
                sideNavActive: {
                    type: Boolean,
                    default: true,
                    required: false
                },
                sideNavPrefix: {
                    type: String,
                    default: "js-side-nav",
                    required: false
                },
                sideNavClass: {
                    type: String,
                    default: "active",
                    required: false
                },
                sectionClass: {
                    type: String,
                    default: "inview",
                    required: false
                }
            },
            directives: {
                scroll: {
                    inserted: function (el, binding, vnode) {
                        let f = function(evt) {
                            if (binding.value(evt, el)) {
                                window.removeEventListener('scroll', f);
                            }
                        };
                        window.addEventListener('scroll', f);
                    }
                },
            },
            data: function () {
                return {
                    scrollService: {},
                    page: {
                        sections: {},
                        sidenavs: {}
                    }
                }
            },
            methods: {
                getAttribute: function(element, key) {
                    return element.getAttribute(key);
                },
                updateViewPort: function() {
                    if (this.scrollService.isInCurrent(window.scrollY)) return;
    
                    [this.page.sections, this.page.sidenavs] = this.scrollService.updateNodeList(window.scrollY);
    
                },
                handleScroll: function(evt, el) {
                    if ( !(this.isScrollInstance()) ) {
                        return this.$nextTick(this.inViewportInit);
                    }
    
                    this.updateViewPort();
                },
                getNodeList: function(key) {
                    this.page[key] = this.scrollService.getNodeList(key);
                },
                isScrollInstance: function() {
                    return this.scrollService instanceof ScrollPageService;
                },
                sideNavInit: function() {
                    if (this.isScrollInstance() && this.scrollService.navInit(this.sideNavPrefix, this.sideNavClass)) this.getNodeList(_N);
                },
                inViewportInit: function() {
                    if (!(this.isScrollInstance()) && ((this.scrollService = new ScrollPageService(this.nodeId, this.sectionClass)) instanceof ScrollPageService)) this.getNodeList(_S);
                },
                isNodeList: function(nodes) {
                    return NodeList.prototype.isPrototypeOf(nodes);
                },
            },
            watch: {
                'page.sections':  {
                    handler(nodeList, oldNodeList){
                        if (this.isNodeList(nodeList) && _.size(nodeList) && this.sideNavActive) {
                            return this.$nextTick(this.sideNavInit);
                        }
                    },
                    deep: true
                },
            },
            mounted() {
                return this.$nextTick(this.inViewportInit);
            },
        }
    
    </script>
    
    
    Vue组件

    <template>
        <div v-scroll="handleScroll">
            <nav class="nav__wrapper" id="navbar-example">
                <ul class="nav">
                    <li role="presentation"
                        :id="[idOfSideNav(key)]"
                        v-for="(item, key,index) in page.sections.items">
                            <a :href="getId(item)">
                            <p class="nav__counter">{{key}}</p>
                                <h3 class="nav__title" v-text="item.getAttribute('data-title')"></h3>
                                <p class="nav__body" v-text="item.getAttribute('data-body')"></p>
                            </a>
                    </li>
                </ul>
            </nav>
    
            <slot></slot>
    
        </div>
    </template>
    
    <script>
        export default {
            name: "ScrollSection",
    
            directives: {
                scroll: {
                    inserted: function (el, binding, vnode) {
                        let f = function(evt) {
                            _.forEach(vnode.context.page.sections.items, function (elem,k) {
                                if (window.scrollY >= elem.offsetTop && window.scrollY <= (elem.offsetTop + elem.offsetHeight)) {
                                    if (!vnode.context.page.sections.items[k].classList.contains("in-viewport") ) {
                                        vnode.context.page.sections.items[k].classList.add("in-viewport");
                                    }
                                    if (!vnode.context.page.sidenavs.items[k].classList.contains("active") ) {
                                        vnode.context.page.sidenavs.items[k].classList.add("active");
                                    }
                                } else {
                                    if (elem.classList.contains("in-viewport") ) {
                                        elem.classList.remove("in-viewport");
                                    }
                                    vnode.context.page.sidenavs.items[k].classList.remove("active");
                                }
                            });
    
                            if (binding.value(evt, el)) {
                                window.removeEventListener('scroll', f);
                            }
                        };
    
                        window.addEventListener('scroll', f);
                    },
                },
    
            },
            data: function () {
                return {
                    page: {
                        sections: {},
                        sidenavs: {}
                    }
                }
            },
            methods: {
                handleScroll: function(evt, el) {
                    // Remove for brevity
                },
                idOfSideNav: function(key) {
                    return "js-side-nav-" + (key+1);
                },
                classOfSideNav: function(key) {
                    if (key==="0") {return "active"}
                },
                elementsOfSideNav:function() {
                    this.page.sidenavs = document.querySelectorAll('*[id^="js-side-nav"]');
                },
                elementsOfSections:function() {
                    this.page.sections = document.querySelectorAll('*[id^="page-section"]');
                },
    
            },
            watch: {
                'page.sections': function (val) {
                    if (_.has(val,'items') && _.size(val.items)) {
                        var self = this;
                        setTimeout(function(){
                            self.elementsOfSideNavs();
                        }, 300);
                    }
                }
            },
            mounted() {
                this.elementsOfSections();
            },
    
        }
    
    
    </script>
    
    
    
    导出默认值{ 名称:“滚动部分”, 指令:{ 滚动:{ 插入:函数(el、绑定、vnode){ 设f=函数(evt){ _.forEach(vnode.context.page.sections.items,函数(elem,k){
    如果(window.scrollY>=elem.offsetTop&&window.scrollY)您可以提供简单的代码笔(或类似的)示例吗?为什么
    :id=“[idOfSideNav(key)]”
    ,您不需要
    []
    围绕id。此外,您可以跳过
    类列表。包含(“在视口中”)
    ,只需使用
    类列表。添加(“在视口中”)
    :添加()添加指定的类值。如果这些类已经存在于元素的class属性中,它们将被忽略。对于remove,它们是相同的。您是否尝试使用refs并注意可能的情况?@ljubaddr谢谢。有效的解决方案已在上面发布。似乎$nextTick()是解决方案。@Gowri谢谢。我无法让$ref正常工作。它似乎是一个儿童-家长可行的解决方案,但似乎不适合多阶段或阶段DOM操作
    <template>
        <div v-scroll="handleScroll">
            <nav class="nav__wrapper" id="navbar-example">
                <ul class="nav">
                    <li role="presentation"
                        :id="[idOfSideNav(key)]"
                        v-for="(item, key,index) in page.sections.items">
                            <a :href="getId(item)">
                            <p class="nav__counter">{{key}}</p>
                                <h3 class="nav__title" v-text="item.getAttribute('data-title')"></h3>
                                <p class="nav__body" v-text="item.getAttribute('data-body')"></p>
                            </a>
                    </li>
                </ul>
            </nav>
    
            <slot></slot>
    
        </div>
    </template>
    
    <script>
        export default {
            name: "ScrollSection",
    
            directives: {
                scroll: {
                    inserted: function (el, binding, vnode) {
                        let f = function(evt) {
                            _.forEach(vnode.context.page.sections.items, function (elem,k) {
                                if (window.scrollY >= elem.offsetTop && window.scrollY <= (elem.offsetTop + elem.offsetHeight)) {
                                    if (!vnode.context.page.sections.items[k].classList.contains("in-viewport") ) {
                                        vnode.context.page.sections.items[k].classList.add("in-viewport");
                                    }
                                    if (!vnode.context.page.sidenavs.items[k].classList.contains("active") ) {
                                        vnode.context.page.sidenavs.items[k].classList.add("active");
                                    }
                                } else {
                                    if (elem.classList.contains("in-viewport") ) {
                                        elem.classList.remove("in-viewport");
                                    }
                                    vnode.context.page.sidenavs.items[k].classList.remove("active");
                                }
                            });
    
                            if (binding.value(evt, el)) {
                                window.removeEventListener('scroll', f);
                            }
                        };
    
                        window.addEventListener('scroll', f);
                    },
                },
    
            },
            data: function () {
                return {
                    page: {
                        sections: {},
                        sidenavs: {}
                    }
                }
            },
            methods: {
                handleScroll: function(evt, el) {
                    // Remove for brevity
                },
                idOfSideNav: function(key) {
                    return "js-side-nav-" + (key+1);
                },
                classOfSideNav: function(key) {
                    if (key==="0") {return "active"}
                },
                elementsOfSideNav:function() {
                    this.page.sidenavs = document.querySelectorAll('*[id^="js-side-nav"]');
                },
                elementsOfSections:function() {
                    this.page.sections = document.querySelectorAll('*[id^="page-section"]');
                },
    
            },
            watch: {
                'page.sections': function (val) {
                    if (_.has(val,'items') && _.size(val.items)) {
                        var self = this;
                        setTimeout(function(){
                            self.elementsOfSideNavs();
                        }, 300);
                    }
                }
            },
            mounted() {
                this.elementsOfSections();
            },
    
        }
    
    
    </script>