current position:Home>[day ui] affix component learning

[day ui] affix component learning

2022-06-24 12:44:51Uncertainty

A fastener component is a component that compares an element of a page to a page HTML Or a dom Internal positioning display , For example, fix the top of the page / The bottom shows , Page width and height change will also keep the original position . If scrolling , Beyond the defined range, it will be fixed , Otherwise, it will scroll with the page

In the last section, we introduced DButton and DIcon The implementation of the , So new affix We will not introduce the file directory structure more . We will mainly learn about the internal implementation , The essence is location , Let's see what judgments and third-party libraries are used , If there is anything wrong, please correct me .

Effect analysis

  1. The first case is that no container is set , According to position Position setting fixed positioning , If the position is set top, So when listening to page scrolling , If the current element's top The value is less than the set offset , Set up fixed location ( conversely bottom Is the more bottom The value is greater than the difference between the page height and the offset fixed location )
  2. The second case is to set the container , that top / bottom Is displayed only in the container , The container is not behind the page , The positioning element disappears . If the top value , So when the current element top The value is less than the offset and the container's bottom Greater than 0, Elements fixed location ( conversely bottom The offset needs to calculate the page height and bottom Worth comparing ). I learned recently that ,fixed Positioning is relative to the window by default , But if you define attributes for the parent node transform、filter、perspective,fixed Positioning is relative to the parent set , If you are interested, you can check by yourself .

The code analysis

dom structure

<template>
  <div ref="root" class="d-affix" :style="rootStyle">
    <!--  Positioning elements   Listen while scrolling  root  The relationship between the position and the viewable area of the page is set  fixed, Set the style when positioning -->
    <div :class="{ 'd-affix--fixed': state.fixed }" :style="affixStyle">
      <slot></slot>
    </div>
  </div>
</template>

Outer layer definition d-affix class , The height is the same as the internal element , To be an internal element fixed When locating out of document flow , The page placeholder structure remains unchanged ; At the same time, we need to compare d-affix Of top and bottom Value to determine when an element leaves the document , When to reset .

attribute

props: {
  //  Locate the level of the element 
  zIndex: {
    type: Number,
    default: 100
  },
  //  In which container , No transmission is the view 
  target: {
    type: String,
    default: ''
  },
  //  Up and down offset 
  offset: {
    type: Number,
    default: 0
  },
  //  From top to bottom 
  position: {
    type: String,
    default: 'top'
  }
},
//  There are two ways to expose to the outside world , Monitor scrolling and  fixed  State change 
emits: ['scroll', 'change'],

setUp The core

//  Locate element attributes 
const state = reactive({
  fixed: false,
  height: 0, // height of target  Get assignment when scrolling 
  width: 0, // width of target
  scrollTop: 0, // scrollTop of documentElement
  clientHeight: 0, //  Window height 
  transform: 0 //  The elements are in  target  When positioning  y  direction 
})

//  Compute properties , Only when scrolling can we get the specific 

// d-affix  Class always exists in the document stream , As long as width and height , Scroll position to determine whether  fixed
const rootStyle = computed(() => {
  return {
    height: state.fixed ? `${state.height}px` : '',
    width: state.fixed ? `${state.width}px` : ''
  }
})
//  Locate element attributes 
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
  }
})

Judgment of positioning attribute during scrolling :

const updateState = () => {
  //  obtain  d-affix  Node information 
  const rootRect = root.value.getBoundingClientRect()
  //  obtain  target  Node information 
  const targetRect = target.value.getBoundingClientRect()
  state.height = rootRect.height
  state.width = rootRect.width
  //  No,  target  take  html  Of  scrollTOP( Yes  target  stay  target  Middle scroll )
  state.scrollTop =
    scrollContainer.value === window
      ? document.documentElement.scrollTop
      : scrollContainer.value.scrollTop

  state.clientHeight = document.documentElement.clientHeight
  //  Set the top margin 
  if (props.position === 'top') {
    if (props.target) {
      //  Locate the element in  target  Sliding distance in element ,bottom  Continuous change 
      const difference = targetRect.bottom - props.offset - state.height
      // target  Elements top Outside the viewing area ,bottom Locate in the visible area 
      state.fixed = props.offset > rootRect.top && targetRect.bottom > 0
      state.transform = difference < 0 ? difference : 0
    } else {
      //  With html Is a relative container , Page scrolling , Fixed position (d-affix  Outside the visual area )
      state.fixed = props.offset > rootRect.top
    }
  } else {
  //  Set bottom margin 
    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 {
      // offset + bottom >  View height , Elements are positioned 
      state.fixed = state.clientHeight - props.offset < rootRect.bottom
    }
  }
}
const onScroll = () => {
  updateState()
  emit('scroll', {
    scrollTop: state.scrollTop,
    fixed: state.fixed
  })
}

watch(
  () => state.fixed,
  () => {
    emit('change', state.fixed)
  }
)
//  When the page is mounted 
onMounted(() => {
  if (props.target) {
    //  Pay attention to the format 
    target.value = document.querySelector(props.target)
    if (!target.value) {
      throw new Error(`target is not existed: ${props.target}`)
    }
  } else {
    target.value = document.documentElement // html
  }
  //  Let's analyze the auxiliary function 
  scrollContainer.value = getScrollContainer(root.value)
  //  Functional programming ,on  Rewritten  addEventListener
  on(scrollContainer.value, 'scroll', onScroll)
  addResizeListener(root.value, updateState)
})
//  The page is about to close. Cancel listening and remove 
onBeforeMount(() => {
  off(scrollContainer.value, 'scroll', onScroll)
  removeResizeListener(root.value, updateState)
})

Auxiliary function

  • on// Functional programming handles element listening export const on = function(element, event, handler, useCapture = false) { if (element && event && handler) { element.addEventListener(event, handler, useCapture) } }export const off = function(element, event, handler, useCapture = false) { if (element && event && handler) { element.removeEventListener(event, handler, useCapture) } }/** * Get the scroll container * @param {*} el Rolling container * @param {*} isVertical Scroll vertically or horizontally * @returns */ export const getScrollContainer = (el, isVertical) => { if (isServer) return let parent = el while (parent) { // None, just window if ([window, document, document.documentElement].includes(parent)) { return window } // Whether the container is scrollable if (isScroll(parent, isVertical)) { return parent } parent = parent.parentNode } return parent }export default typeof window === 'undefined'/** * * @param {*} el * @param {*} isVertical Is it vertical overflow-y * @returns */ export const isScroll = (el, isVertical) => { if (isServer) return const determineDirection = isVertical === null || isVertical === undefined const overflow = determineDirection ? getStyle(el, 'overflow') : isVertical ? getStyle(el, 'overflow-y') : getStyle(el, 'overflow-x') return overflow.match(/(scroll|auto)/) }// Gets the attribute value of the element export const getStyle = function(element, styleName) { if (isServer) return if (!element || !styleName) return null styleName = camelize(styleName) if (styleName === 'float') { /** * ie6~8 Next :style.styleFloat FF/chrome as well as ie9 above :style.cssFloat */ styleName = 'cssFloat' // FF/chrome as well as ie9 above float Compatibility writing } try { const style = element.style[styleName] if (style) return style // obtain window object , firefox Low version 3.6 Can be used getComputed Method ,iframe pupup extension window === document.defaultView, Otherwise, the point is wrong // https://www.cnblogs.com/yuan-shuai/p/4125511.html const computed = document.defaultView.getComputedStyle(element, '') return computed ? computed[styleName] : '' } catch (e) { return element.style[styleName] } }resize-observer-polyfill This library is the first time I have seen , If you don't know the source code . I think it's very interesting , Here is a brief introduction .
  • off
  • getScrollContainer
  • isSserver
  • isScroll
  • getStyle

The main function of this library is to listen for elements size change . Usually, we can only use to monitor the size change window.size perhaps window.orientationchange( The mobile terminal screen displays horizontally and vertically ).resize The event will be in 1s Internal trigger 60 Times or so , Therefore, it is easy to cause performance problems when changing the window size , So when we listen for changes in an element, it seems a bit wasteful .

ResizeObserver API It's new , There is also compatibility in some browsers , This library is very compatible .ResizeObserver Using observer mode , When element size Trigger when a change occurs ( The presence of hidden nodes will also trigger ).

usage

const observer = new ResizeObserver(entries => {
  entries.forEach(entry => {
    console.log(' Size position ', entry.contentRect)
    console.log(' Monitoring dom', entry.target)
  })
})
//  The object of listening is body, You can change the size of the browser window to see the printing effect 
observer.observe(document.body)// dom node , It's not a class name  id name 
  • width: Refers to the width of the element itself , It doesn't contain padding,border value
  • height: Refers to the height of the element itself , It doesn't contain padding,border value
  • top: finger padidng-top Value
  • left: finger padding-left Value
  • right: finger left + width Value
  • bottom: value top + height Value

Method

  • ResizeObserver.disconnect() Cancel listening for all elements
  • ResizeObserver.observe() Monitor elements
  • ResizeObserver.unobserve() End listening for an element

Components use

We are onMounted Chinese vs root Element listening . Listen while scrolling , Element size changes should also be monitored

import ResizeObserver from 'resize-observer-polyfill'
import isServer from './isServer'

const resizeHandler = function(entries) {
  for (const entry of entries) {
    /**
     * const {left, top, width, height} = entry.contentRect;
     * 'Element:', entry.target
        Element's size: ${ width }px x ${ height }px`
        Element's paddings: ${ top }px ; ${ left }px`
     */
    const listeners = entry.target.__resizeListeners__ || []
    if (listeners.length) {
      //  Element changes the direct execution method 
      listeners.forEach(fn => fn())
    }
  }
}
//  monitor element Elements size change , perform fn
export const addResizeListener = function(element, fn) {
  if (isServer || !element) return
  if (!element.__resizeListeners__) {
    element.__resizeListeners__ = []
    /**
     * https://github.com/que-etc/resize-observer-polyfill
     *
     */
    element.__ro__ = new ResizeObserver(resizeHandler)
    //  The object of observation 
    element.__ro__.observe(element)
  }
  element.__resizeListeners__.push(fn)
}
//  Exit remove listening 
export const removeResizeListener = function(element, fn) {
  if (!element || !element.__resizeListeners__) return
  element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1)
  if (!element.__resizeListeners__.length) {
    //  Cancel monitoring 
    element.__ro__.disconnect()
  }
}

That's right affix Component learning . If there is any mistake, please correct it . Next, let's go to alert Component learning . If the article helps you , Welcome to the official account Touch the front end , Or add wechat :wajh123654789, Learning together .

copyright notice
author[Uncertainty],Please bring the original link to reprint, thank you.
https://en.chowdera.com/2022/175/20210526152008381F.html

Random recommended