The pin component is used to position a page element relative to the PAGE HTML or a DOM, such as the top/bottom of the page display, the page width and height changes will remain the same position. If you scroll beyond the defined range, it will be fixed, otherwise it will follow the page scroll

In the last section we introduced the implementation of DButton and DIcon, so we won’t cover the new affix file directory structure. We’re going to focus on internal implementations, which are essentially location-based, and we’re going to look at what judgments and third party libraries are being used, and we’re welcome to point out if anything is wrong.

Effect analysis

  1. The first case is that the container is not setpositionPosition setting fixed position if position settingtop, so when listening to the page scroll, if the current elementtopValue less than the set offset, setfixedPosition (vice versabottomIs the morebottomValue greater than the difference between page height and offset settingfixedPositioning)
  2. The second case is to set the container, sotop / bottomIs displayed only within the container, and the positioning element disappears when the container is no longer on the page. If set totopValue, then when the current elementtopValue less than the offset and containerbottomGreater than 0, the elementfixedPosition (vice versabottomThe offset needs to calculate the page height sumbottomWorth comparing).

As we have learned recently, fixed positioning is relative to the window by default, but if the parent node defines the attributes transform, filter, perspective, fixed positioning will be relative to the parent set. If you are interested, you can check it by yourself.

The code analysis

Dom structure

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

The outer layer defines the D-Affix class, which is the same height as the inner element, so that the page placeholder structure does not change when the inner element is fixed out of the document flow. You also need to compare the top and bottom values of D-affix to determine when an element is out of the document and when it is reset.

attribute

ZIndex: {type: Number, default: 100}, // in which container, the view is not supported. Target: {type: String, default: }, // Offset: {type: Number, default: 0}, // offset: {type: Number, default: 0}, // offset: {type: Number, default: 0} emits: ['scroll', 'change'],Copy the code

The setUp of the core

Const state = reactive({fixed: false, height: 0, // height of target) 0, // width of target scrollTop: 0, // scrollTop of documentElement clientHeight: 0, // window height transform: // The d-affix class is always in the document stream, as long as the width and height, Computed (() => {return {height: state.fixed? `${state.height}px` : '', width: state.fixed ? '${state.width}px' : '}}) 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

Judgment of positioning properties during scrolling:

Const updateState () = = > {/ / d - affix node information const rootRect. = root value. GetBoundingClientRect () / / for the target node information 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. The documentElement. ClientHeight / / set the top margin if (props. The position === 'top') {if (props. Target) { Const difference = targetRect.bottom-props. Offset-state. height // target element top outside the viewable area, State. fixed = props. Offset > rootRect.top && targetRect.bottom > 0 state.transform = difference < 0? difference : 0} else {// with HTML as the relative container, the page scroll, Fixed = props. Offset > rootRect.top}} else {// Set the lower offset 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, element positioning state.fixed = state.clientheight - props. Offset < rootRect.bottom}}Copy the code
const onScroll = () => { updateState() emit('scroll', { scrollTop: state.scrollTop, fixed: state.fixed }) } watch( () => state.fixed, () => { emit('change', // if (props. Target) {// if (props. Target) {// If (props document.querySelector(props.target) if (! target.value) { throw new Error(`target is not existed: ${props. Target} ')} else {target.value = document.documentElement // HTML} // scrollContainer.value = GetScrollContainer (root.value) // Function programming, Value, 'scroll', onScroll) addResizeListener(root.value, Cancel listening remove onBeforeMount(() => {off(scrollContainer.value, 'scroll', onScroll) removeResizeListener(root.value, updateState) })Copy the code

Auxiliary function

  • on
Export const on = function(element, event, handler, useCapture = false) { if (element && event && handler) { element.addEventListener(event, handler, useCapture) } }Copy the code
  • off
export const off = function(element, event, handler, useCapture = false) {
  if (element && event && handler) {
    element.removeEventListener(event, handler, useCapture)
  }
}
Copy the code
  • getScrollContainer
@param {*} isVertical or horizontal scrolling * @returns */ export const getScrollContainer = (el, isVertical) => {if (isServer) return let parent = el while (parent) { Document.documentelement].includes(parent)) {return window} if (isScroll(parent, isVertical)) { return parent } parent = parent.parentNode } return parent }Copy the code
  • isSserver
export default typeof window === 'undefined'
Copy the code
  • isScroll
/** ** @param {*} el * @param {*} isVertical 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)/) }Copy the code
  • getStyle
Export const getStyle = function(Element, styleName) {if (isServer) return if (! element || ! StyleName) return null styleName = camelize(styleName) if (styleName === 'float') {/** * StyleFloat FF/ Chrome and IE9 above: CssFloat */ styleName = 'cssFloat' // FF/ Chrome} try {const style = element.style[styleName] If (style) return style // Get the window object, firefox 3.6 can use getComputed method, If pupup extension window === document. DefaultView, Otherwise the pointing error / / 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] } }Copy the code

Resize – the observer – polyfill library

This library is the first time I see, if do not read the source do not know. Think still very interesting, here is a brief introduction.

The main function of this library is to listen for element size changes. Normally we can only listen for size changes using window.size or window.orientationchange(horizontal and vertical display on the mobile screen). The resize event fires about 60 times in 1s, so it’s easy to cause performance problems when changing the window size, so it’s wasteful to listen for changes in an element.

The ResizeObserver API is new and compatible with some browsers, so the library works well. ResizeObserver uses the observer mode, which is triggered when the element size changes (as is the appearance and hiding of nodes).

usage

Const Observer = new ResizeObserver(entries => {entries.foreach (entry => {console.log(' size position ', ContentRect) console.log(' listening dom', entry.target)})}) Observer.observe (document.body)// DOM node, not class name id nameCopy the code

  • width: indicates the width of the element itselfPadding, border
  • height: indicates the height of the element itselfPadding, border
  • top: refers to thepadidng-topThe value of the
  • left: refers to thepadding-leftThe value of the
  • right: refers to theleft + widthThe value of the
  • bottomValue:top + heightThe value of the

methods

  • ResizeObserver.disconnect()Unlisten all elements
  • ResizeObserver.observe()Listening to the element
  • ResizeObserver.unobserve()End listening for an element

Components use

We listen on the root element in onMounted. Listen for scrolling, as well as changes in element size

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__ | | [] the if (listeners. Length) {/ / element change execution method directly Listeners. ForEach (fn => fn())}} 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 to be observed Element.__ro__. observe(element)} element.__resizelisteners__. push(fn)} export const removeResizeListener = function(element, fn) { if (! element || ! element.__resizeListeners__) return element.__resizeListeners__.splice(element.__resizeListeners__.indexOf(fn), 1) if (! Element.__resizelisteners__.length) {// Cancel listening on element.__ro__.disconnect()}}Copy the code

So that’s the affix component. You are welcome to correct me. In the next article, we will study the Alert component.