tab组件开发过程中的一些坑和收获

Paste_Image.png

具体交互参考ant.design.

首先是关于angular的指令
指令内部的初始化顺序: controller函数 => link函数.我们执行这样一段代码:

        controller: /*@ngInject*/($scope) => {
            console.log($scope.test)  //undefined
        },
        link: (scope, element, attr) => {
            scope.test = attr.testAttr
            console.log(scope.test)  //hello world
        }

第一次console.log 会输出 undefined

link函数内部的dom操作
我们知道,在link函数内执行dom操作是安全的,同时也应该这样做.看代码(这是指令的html模板):

        
{{row.name}}

这时在link函数内执行:

const nodes = document.querySelector('.parentNode').children
console.log(nodes)

查看控制台,会发生一个奇怪的现象:

tab组件开发过程中的一些坑和收获_第1张图片
Paste_Image.png

我们看到了 想要的子节点,但是当我们 console.log(nodes[2])时,却显示undefined.这是为啥???

这个问题困扰了我好久.直到我想到:子节点有一部分是通过ng-repeat指令渲染的.当tabList还未获取到的时候,我们通过dom获取子节点是有问题的...想通了这一点后,修改代码为:

            scope.$watch('tabList', () => {
                const nodes = document.querySelector('.parentNode').children
                console.log(nodes[2])  //正常显示

            })

ok,我们取到了我们想要的节点.终于可以开始计算节点位置,滚动多远等等...

但是当我实际计算好多次却仍然发现滚动距离有误,并且每次结果还不一致时,我意识到:
光watch tabList还不够,这时的dom元素根本还没有稳定下来!计算取的数值当然也就不对了..
于是有下面一段代码:

                let close = setInterval(() => {
                    const nodes = document.querySelector('.parentNode').children
                    //避免节点为空报错
                    if (nodes.length < 2){
                        return
                    }
                    let {width: rw, right: rr} = nodes[nodes.length - 2].getBoundingClientRect()

                    setTimeout(() => {
                        const nodes = document.querySelector('.parentNode').children
                        let {width, right} = nodes[nodes.length - 2].getBoundingClientRect()
                        if (Math.abs(width - rw) < 1 && Math.abs(right - rr) < 1){
                            //容器稳定后取消定时器
                            clearInterval(close)
                            redraw() 
                            scope.$apply()                                            
                        }
                    }, 1000 / 100)
                }, 1000 / 60)

核心是我们需要设置一个定时器setInterval,不断的检测当前容器的宽度.当容器宽度不再变化,稳定下来以后,我们取消定时器,并执行计算相关逻辑.终于可以愉快的计算了...

关于dom节点
dom节点本身是一个对象,可以作为函数参数传递;
具有对象引用的那些特性,如果先用变量(类型为对象)存储子节点,之后子节点的位置发生了变化,后面再从变量中读取子节点的位置信息,获取的是变化过后的数值

tips
node.getBoundingClientRect()
很好用的一个api,返回一个对象,包含元素当前的位置,自身宽高等属性

关于事件节流
当我们监控屏幕尺寸变化时,window.addEventListener('resize', fn),如果用户拖动浏览器,一个很普通的操作也会不断的触发fn,严重影响性能.so,我们需要事件节流.

节流函数代码如下:

            function throttle (fn, delay, minSpacing) {
                let timer = null
                let previous = null

                return function(){
                    let now = new Date()
                    if (!previous){
                        previous = now
                    }
                    if (minSpacing && now - previous > minSpacing){
                        fn()
                        previous = now
                        clearTimeout(timer)
                    }else {
                        clearTimeout(timer)
                        timer = setTimeout(() => {
                            fn()
                            previous = null
                        }, delay)
                    }
                }
            }

核心其实就是不断的清除之前的定时器,然后生成新定时器,保证核心代码不会被一直执行.这里设置了一个函数执行的最小时间间隔(minSpacing)

如何让元素左右滚起来
布局上的一点技巧,设置一个父容器div,overflow-x: hidden,子容器设置一个非常大的宽度类似width: 500%,子容器内部的元素 inline-block展示,这时想让元素滚动起来只需要对子容器做平移变换就可以了.

写在后面(一些闲话)

  • 最开始没有搞清楚需求就开始开发浪费了不少时间,拖延进度(当然对于知识的学习肯定是有用的,关于directive的一些之后总结);
  • 实际计算距离绘制的时候也有不少坑,包括从tabList中获取到的当前tab的index和实际tab在子节点中的index是不一致的(子节点中为了样式考虑插入了额外的节点,这段代码是既有的导致开始根本没意识到这个问题)
  • 作为一个前端,自己也应当充当好产品的角色,开发之前想清楚详细的交互.否则后续代码可能会面临各种修改.
  • 想清楚临界条件.
  • 左右翻页时,用栈来存储每次向右偏移的想法挺不错.(虽然最后去掉了这种做法,屏幕尺寸变换,中英文切换,页面初始化时都需要重新滚动,维护stack的成本太大了) 每次向右滚动执行stack.push(offset),向左翻页时则执行let offset = stack.pop()省去了一半的计算量.

你可能感兴趣的:(tab组件开发过程中的一些坑和收获)