I. Component introduction

The Affix component is used to anchor page elements in a specific visual area.

1.1 attributes

  • Position: Specifies the position of the fasteners. The value can be top or bottom. The default value is top
  • Offset: Sets the offset distance. The default value is 0
  • Target: specifies the container (CSS selector) that keeps the studs inside the container and hidden beyond the range. The default container isdocument.documentElement.
  • Z-index: specifies the level of fixing. The default value is 100

1.2 event

  • Scroll: event triggered when the container is rolling. Parameters are: scrollTop value and status (fixed or not) of the peg.
  • Change: triggered when the fixed state changes. The parameter is whether the fixed state is currently fixed

Second, source code analysis

2.1 the template

<template>
  <div ref="root" class="el-affix" :style="rootStyle">
    <div :class="{'el-affix--fixed': state.fixed}" :style="affixStyle">
      <slot></slot>
    </div>
  </div>
</template>
Copy the code

The Template part is simple, receiving content through slots

2.2 the script

// Part of the core code, code order has been adjusted
setup(props, { emit }) {
    // Target container ref
    const target = ref(null) 
    // Fix ref, which works with the ref attribute in the template to get the HTML element
    const root = ref(null)
    // Scroll the container ref
    const scrollContainer = ref(null)
    
    // The state of the nail
    const state = reactive({
      fixed: false.height: 0.// height of root
      width: 0.// width of root
      scrollTop: 0.// scrollTop of documentElement
      clientHeight: 0.// clientHeight of documentElement
      transform: 0,
    })
    
    onMounted(() = > {
      // Determine the target container based on the target passed in
      if (props.target) {
        target.value = document.querySelector(props.target)
        if(! target.value) {throw new Error(`target is not existed: ${props.target}`)}}else {
        target.value = document.documentElement
      }
      
      // Look up the scroll container based on the pin element
      scrollContainer.value = getScrollContainer(root.value)
      // Monitor the scroll event of the scroll container
      on(scrollContainer.value, 'scroll', onScroll)
      // Listen for the resize event of the pin element
      addResizeListener(root.value, updateState)
    })
    
    // The response function to the scroll event of the scroll container
    const onScroll = () = > {
      // Update the stapler status
      updateState()
      
      emit('scroll', {
        scrollTop: state.scrollTop,
        fixed: state.fixed,
      })
    }
    
    // Update the stapler state function
    const updateState = () = > {
      const rootRect = root.value.getBoundingClientRect()
      const targetRect = target.value.getBoundingClientRect()
      state.height = rootRect.height
      state.width = rootRect.width
      state.scrollTop = scrollContainer.value === window ? document.documentElement.scrollTop : scrollContainer.value.scrollTop
      state.clientHeight = document.documentElement.clientHeight

      if (props.position === 'top') {
        if (props.target) {
          const difference = targetRect.bottom - props.offset - state.height
          // targetrect. bottom > 0 corresponds to keeping the studs inside the container and hiding them beyond the range
          state.fixed = props.offset > rootRect.top && targetRect.bottom > 0
          // For processing scenarios: during scrolling, if the target container is not visible enough to display the entire nail, the nail should be offset accordingly and only part of the nail should be displayed
          state.transform = difference < 0 ? difference : 0
        } else {
          state.fixed = props.offset > rootRect.top
        }
      } else {
        if (props.target) {
          const difference = state.clientHeight - targetRect.top - props.offset - state.height
          state.fixed = state.clientHeight - props.offset < rootRect.bottom && state.clientHeight > targetRect.top
          state.transform = difference < 0 ? -difference : 0
        } else {
          state.fixed = state.clientHeight - props.offset < rootRect.bottom
        }
      }
    }
    // Monitor fixed status change and emit change event
    watch(() = > state.fixed, () = > {
      emit('change', state.fixed)
    })
    
    // Calculate the properties to automatically update the style of the stud via the status of the stud
    const affixStyle = computed(() = > {
      if(! state.fixed) {return
      }
      const offset = props.offset ? `${props.offset}px` : 0
      const transform = state.transform ? `translateY(${state.transform}px)` : ' '

      return {
        height: `${state.height}px`.width: `${state.width}px`.top: props.position === 'top' ? offset : ' '.bottom: props.position === 'bottom' ? offset : ' '.transform: transform,
        zIndex: props.zIndex,
      }
    })
}
Copy the code

2.3 Realization summary:

  1. By listening to the scroll event of the rolling container (and the resize event of the fixation itself);

  2. The event response function dynamically obtains the DOM properties of the fixer and the target container and calculates the state of the fixer.

  3. Using computational properties to automatically update the style of the stud;