美文网首页
造轮子-sticky组件

造轮子-sticky组件

作者: sweetBoy_9126 | 来源:发表于2019-05-20 21:11 被阅读0次

最初的api设计

<lf-sticky distance="100">
  <div>我是粘住的内容</div>
</lf-sticky>

上面的api就是接受一个distance参数,意思是距离顶部多少距离的时候固定住

当需要通过滚动事件获取当前元素距离文档顶部的距离时,不能只使用getBoundClinetRect这个api,因为他获取的是距离窗口顶部的位置,也就是下面的109,如果要获取当前元素剧文档的距离就需要使用getBoundClientRect.top+window.scrollY

  1. 简单实现
    sticky.vue
<template>
    <div class="lifa-sticky" ref="sticky" :class="classes">
        <slot></slot>
    </div>
</template>

<script>
    export default {
        name: "LiFaSticky",
        data() {
            return {
                sticky: false
            }
        },
        created() {

        },
        mounted() {
            window.addEventListener('scroll', () => {
                if(window.scrollY > this.top()) {
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            })
        },
        methods: {
            top() {
                let {top} = this.$refs.sticky.getBoundingClientRect()
                top = top + window.scrollY
                return top
            }
        },
        computed: {
            classes() {
                return {
                    sticky: this.sticky
                }
            }
        }
    }
</script>

<style scoped lang="scss">
    .lifa-sticky{
        &.sticky {
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
        }
    }
</style>

问题1:当元素固定的时候会一直闪?
原因:因为我们fixed定位后,元素距离文档顶部的top就无法确定了,所以我们只能取一次,也就是把最初的top确定下来,而不是每次滚动的时候都去重新获取top

 mounted() {
            let top = this.top()
            window.addEventListener('scroll', () => {
                if(window.scrollY > top) {
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            })
        },

问题2:当文档内容较少刚好达到显示滚动条的程度,你滚动滚动条会发现无法滚动,然后页面一直再闪
原因:

以上面图为例,比如说我们窗口最多显示9行内容,默认一开始顶部的元素还没sticky状态在文档流中,所以9就超出了窗口就会显示滚动条,而当我们滚动的时候,顶部的元素就直接会变成fixed定位脱离文档流,这时候页面中只剩1-9,正好是窗口显示的高度,所以滚动条消失,就不能滚动,滚动高度就是0,因此顶部的元素又进入了文档流,9又出去了又出现滚动条了,一直如此循环
解决办法:不要让当前元素脱离文档流,而是让他的子元素脱离,因为子元素脱离文档流了,所以会造成父元素没有高度,这时我们需要在一开始的时候获取到它的高度然后赋值给它

<template>
    <div class="lifa-sticky-wrapper" ref="sticky">
        <div class="lifa-sticky" :class="classes">
            <slot></slot>
        </div>
    </div>
</template>

<script>
    export default {
        name: "LiFaSticky",
        data() {
            return {
                sticky: false
            }
        },
        created() {

        },
        mounted() {
            // 在一开始还没有脱离文档流的时候获取一下父元素的高度,然后将他的
            // 高度赋值给它,这样当脱离文档流的时候他也会有高度
            let {top,height} = this.topAndHeight()
            this.$refs.sticky.style.height = `${height}px`
            window.addEventListener('scroll', () => {
                if(window.scrollY > top) {
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            })
        },
        methods: {
            topAndHeight() {
                let {top,height} = this.$refs.sticky.getBoundingClientRect()
                top = top + window.scrollY
                return {top,height}
            }
        },
    }
</script>

问题3:由于我们是在一开始就获取高度,如果这个子元素里有服务端获取的图片什么的,有可能因为网络原因我们不能获取到准确的高度
解决方法:在滚动的时候去获取它的高度

<lf-sticky distance="100">
            <div style="border: 1px solid red;">
                <img src="https://i.loli.net/2019/05/20/5ce2b21628eab50666.jpg" alt="">
            </div>
        </lf-sticky>

mounted(){
  window.addEventListener('scroll', () => {
                if(window.scrollY > top) {
                    // 滚动的时候直接获取高度,防止因为网络延迟原因获取高度不准确
                    let height = this.height()
                    this.$refs.sticky.style.height =`${height}px`
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            })
},
methods: {
  height() {
                let {height} = this.$refs.sticky.getBoundingClientRect()
                return height
            }
}

问题4:如果像上图中我们的内容宽度不是百分之百的,那么我们就不能直接在css里设置width和left,所以我们也要通过js获取

<template>
    <div class="lifa-sticky-wrapper" ref="sticky" :style="{height}">
        <div class="lifa-sticky" :class="classes" :style="{left,width}">
            <slot></slot>
        </div>
    </div>
</template>

<script>
    export default {
        name: "LiFaSticky",
        data() {
            return {
                sticky: false,
                height: undefined,
                width: undefined,
                left: undefined
            }
        },
        created() {

        },
        mounted() {
            // 在一开始还没有脱离文档流的时候获取一下父元素的高度,然后将他的
            // 高度赋值给它,这样当脱离文档流的时候他也会有高度
            let top = this.top()
            window.addEventListener('scroll', () => {
                if(window.scrollY > top) {
                    // 滚动的时候直接获取高度,防止因为网络延迟原因获取高度不准确
                    let {left,width,height} = this.$refs.sticky.getBoundingClientRect()
                    this.left = left + 'px'
                    this.height = height + 'px'
                    this.width = width + 'px'
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            })
        },
        methods: {
            top() {
                let {top} = this.$refs.sticky.getBoundingClientRect()
                top = top + window.scrollY
                return top
            }
        },
        computed: {
            classes() {
                return {
                    sticky: this.sticky
                }
            }
        }
    }
</script>

<style scoped lang="scss">
    .lifa-sticky{
        &.sticky {
            position: fixed;
            top: 0;
        }
    }
</style>

热更新与beforeDestroy

当我们修改代码的时候如果不自己刷新页面就会发现控制台会报错,原因是webpack的热更新机制,当我们修改代码的时候它会帮我们刷新对应的组件,也就是把之前的组件从页面移除,把新的组件加到页面中的相同位置,而我们旧的组件在监听当前窗口的scroll事件,新的也在监听,而旧的组件的dom已经从页面移除了,所以获取不到对应的值

技巧:如果你的组件做了任何有副作用的操作(你改了除了自己以外的任何东西,常见的就是window里的事件监听还有setInterval),这里因为你修改了window,你就必须在beforeDestory中把这个副作用清除掉

mounted() {
            // 在一开始还没有脱离文档流的时候获取一下父元素的高度,然后将他的
            // 高度赋值给它,这样当脱离文档流的时候他也会有高度
            let top = this.top()
            this.windowScrollHandler = () => {
                if(window.scrollY > top) {
                    // 滚动的时候直接获取高度,防止因为网络延迟原因获取高度不准确
                    let {left,width,height} = this.$refs.sticky.getBoundingClientRect()
                    this.left = left + 'px'
                    this.height = height + 'px'
                    this.width = width + 'px'
                    this.sticky = true
                }else {
                    this.sticky = false
                }
            }
            window.addEventListener('scroll', this.windowScrollHandler)
        },
        methods: {
            top() {
                let {top} = this.$refs.sticky.getBoundingClientRect()
                top = top + window.scrollY
                return top
            }
        },
        computed: {
            classes() {
                return {
                    sticky: this.sticky
                }
            }
        },
        beforeDestroy() {
            window.removeEventListener('scroll',this.windowScrollHandler)
        }
添加distance
<lf-sticky :distance="100">
      <div style="border: 1px solid red;">
          <img src="https://i.loli.net/2019/05/20/5ce2b50b09e2843554.png" alt="">
      </div>
</lf-sticky>
  • sticky.vue
<div class="lifa-sticky-wrapper" ref="sticky" :style="{height}">
        <div class="lifa-sticky" :class="classes" :style="{left,width,top}">
            <slot></slot>
        </div>
    </div>

props: {
          distance: {
              type: Number,
              default: 0
          }
        },
data() {
            return {
+                top: undefined
            }
        },
mounted() {
            // 在一开始还没有脱离文档流的时候获取一下父元素的高度,然后将他的
            // 高度赋值给它,这样当脱离文档流的时候他也会有高度
            let top = this.offsetTop()
            this.windowScrollHandler = () => {
                if(window.scrollY > top - this.distance) {
                    // 滚动的时候直接获取高度,防止因为网络延迟原因获取高度不准确
                    let {left,width,height} = this.$refs.sticky.getBoundingClientRect()
                    this.left = left + 'px'
                    this.height = height + 'px'
                    this.width = width + 'px'
                    this.sticky = true
                    this.top = this.distance + 'px'
                }else {
                    this.sticky = false
                }
            }
            window.addEventListener('scroll', this.windowScrollHandler)
        },
        methods: {
            offsetTop() {
                let {top} = this.$refs.sticky.getBoundingClientRect()
                top = top + window.scrollY
                return top
            }
        },

相关文章

  • 造轮子-sticky组件

    最初的api设计 上面的api就是接受一个distance参数,意思是距离顶部多少距离的时候固定住 当需要通过滚动...

  • 造轮子-nav组件

    补充知识:如果你需要相对引用你得加一个./否则会被认为你是在引用一个第三方库比如: 首先我们需要三个组件分别是na...

  • 简单造轮子代码

    Tabs组件 Sticky组件 Dialog组件 Suggestion组件

  • 2019-05-31 程序员修仙进阶标准,你到哪个阶段了?

    闭门造轮子 > 使用别人的轮子 > 开门造轮子 > 分享轮子

  • 造轮子-tab组件(上)

    1. 如何解决之前遗留的bug 根据错误提示大概确定原因,toast.test.js .style 造成。 用二分...

  • 造轮子-tab组件(中)

    1. 如果给一个标签一个class,标签本身又有class,vue是默认会合并的。只有两个属性是这样一个是clas...

  • 造轮子之仿射变换

    有人说,我们不应该再造轮子;也有人说,学习怎么造轮子可以带来更深的理解。我说,用轮子有用轮子的乐趣,造轮子有造轮子...

  • 第2章 组件化选型

    1、组件化选型结论 方案是Arouter+auto-register+自己造轮子补充原因:arouter使用简单,...

  • Android模块化开发实践

    重复造轮子是肯定的 区分概念(组件化,模块化,插件化) 1.组件化封装可重用功能代码,例如网络组件,数据库组件,图...

  • 开源时代,一杯敬明天,一杯敬过往

    Reinvent the Wheel 从“我们不要重复造轮子”到,兄弟们“我们造轮子”吧 不要重复造轮子 意味着我...

网友评论

      本文标题:造轮子-sticky组件

      本文链接:https://www.haomeiwen.com/subject/fsmlzqtx.html