具体交互参考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)
查看控制台,会发生一个奇怪的现象:
我们看到了 想要的子节点,但是当我们
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()
省去了一半的计算量.