当前位置: 首页 > 工具软件 > Affix > 使用案例 >

iview/viewUI库组件封装学习——Affix

高溪叠
2023-12-01

基本逻辑:该UI组件是在满足特定条件下触发fixed效果,和css3中得sticky很相近。首先此组件涉及三个主要参数,分别是滚动条的滚动距离scrollTop(window.scrollTop)、组件元素的BoundingClientRect.top 即该组件距离浏览器可视区域的距离,还有组件接收用户参数offsetTop的值,如何保证元素距离可视区顶部为offsetTop的值时将元素固定?BoundingClientRect.top的初始值即scrollTop为0时,当我们滚动滚动条时BoundingClientRect.top减小到scrollTop时触发固定事件,即触发事件的条件为BoundingClientRect.top的初始值 — scrollTop (滚动条滚动的距离)< offsetTop(用户设定的边界值)触发固定条件 即组件的class = ivu-affix 为true,同时触发事件函数on-change,bottom和left同理,(有点凌乱,最后附上源码参考理解)
总结: 固定元素其实很简单,但通过复杂js实现此功能实现了2点功能:1、可以设定触发条件瞬间的回调函数(on-change)2、同时用position fixed 完成了position sticky效果,增加了兼容性(CSS3才开始支持position: sticky)
源码中on和off是公共类的事件绑定和解绑的兼容性写法,可以当作addEventListener函数理解。

<template>
    <div>
        <div ref="point" :class="classes" :style="styles">
            <slot></slot>
        </div>
        <div v-show="slot" :style="slotStyle"></div>
    </div>
</template>
<script>
    import { on, off } from '../../utils/dom';
    const prefixCls = 'ivu-affix';

    function getScroll(target, top) {
        const prop = top ? 'pageYOffset' : 'pageXOffset';
        const method = top ? 'scrollTop' : 'scrollLeft';

        let ret = target[prop];

        if (typeof ret !== 'number') {
            ret = window.document.documentElement[method];
        }

        return ret;
    }

    function getOffset(element) {
        const rect = element.getBoundingClientRect();

        const scrollTop = getScroll(window, true);
        const scrollLeft = getScroll(window);

        const docEl = window.document.body;
        const clientTop = docEl.clientTop || 0;
        const clientLeft = docEl.clientLeft || 0;
        return {
            top: rect.top + scrollTop - clientTop, //rect.top affix元素距离可视窗顶部得距离
            left: rect.left + scrollLeft - clientLeft
        };
    }

    export default {
        name: 'Affix',
        props: {
            offsetTop: {
                type: Number,
                default: 0
            },
            offsetBottom: {
                type: Number
            },
            useCapture: {
                type: Boolean,
                default: false
            }
        },
        data () {
            return {
                affix: false,
                styles: {},
                slot: false,
                slotStyle: {}
            };
        },
        computed: {
            offsetType () {
                let type = 'top';
                if (this.offsetBottom >= 0) {
                    type = 'bottom';
                }

                return type;
            },
            classes () {
                return [
                    {
                        [`${prefixCls}`]: this.affix
                    }
                ];
            }
        },
        mounted () {
//            window.addEventListener('scroll', this.handleScroll, false);
//            window.addEventListener('resize', this.handleScroll, false);
            on(window, 'scroll', this.handleScroll, this.useCapture);
            on(window, 'resize', this.handleScroll, this.useCapture);
            this.$nextTick(() => {
                this.handleScroll();
            });
        },
        beforeDestroy () {
//            window.removeEventListener('scroll', this.handleScroll, false);
//            window.removeEventListener('resize', this.handleScroll, false);
            off(window, 'scroll', this.handleScroll, this.useCapture);
            off(window, 'resize', this.handleScroll, this.useCapture);
        },
        methods: {
            handleScroll () {
                const affix = this.affix; //默认false
                const scrollTop = getScroll(window, true); //滚动条滚动距离
                const elOffset = getOffset(this.$el); // affix元素得offset 
                const windowHeight = window.innerHeight;//可视窗口高度
                const elHeight = this.$el.getElementsByTagName('div')[0].offsetHeight; //affix元素offsetHeight
                // Fixed Top
                if ((elOffset.top - this.offsetTop) < scrollTop && this.offsetType == 'top' && !affix) {
                    this.affix = true;
                    this.slotStyle = {
                        width: this.$refs.point.clientWidth + 'px',
                        height: this.$refs.point.clientHeight + 'px'
                    };
                    this.slot = true;
                    this.styles = {
                        top: `${this.offsetTop}px`,
                        left: `${elOffset.left}px`,
                        width: `${this.$el.offsetWidth}px`
                    };

                    this.$emit('on-change', true);
                } else if ((elOffset.top - this.offsetTop) > scrollTop && this.offsetType == 'top' && affix) {
                    this.slot = false;
                    this.slotStyle = {};
                    this.affix = false;
                    this.styles = null;

                    this.$emit('on-change', false);
                }

                // Fixed Bottom
                if ((elOffset.top + this.offsetBottom + elHeight) > (scrollTop + windowHeight) && this.offsetType == 'bottom' && !affix) {
                    this.affix = true;
                    this.styles = {
                        bottom: `${this.offsetBottom}px`,
                        left: `${elOffset.left}px`,
                        width: `${this.$el.offsetWidth}px`
                    };

                    this.$emit('on-change', true);
                } else if ((elOffset.top + this.offsetBottom + elHeight) < (scrollTop + windowHeight) && this.offsetType == 'bottom' && affix) {
                    this.affix = false;
                    this.styles = null;

                    this.$emit('on-change', false);
                }
            }
        }
    };
</script>

 类似资料: