react component encapsulation: the box in the visual page gradually floats up

demand

  • When sliding to the visual window, it gradually appears from bottom to top
  • It does not affect the layout structure of the internal box
  • Height customization (delay time, sinking distance)

Renderings (gif slower, actually faster):

realization

reflection

For such requirements, since many parameters need to be filled with variables and the internal box must not be affected, the absolute positioning is excluded first, and transform:translate(l/r,t/b) is used to transform a position. Then complete the fade out with opacity. Set the transition to determine the delay duration

step

  • First, it has to be a component
  • Get all the target DOMS of the current page
  • Judge whether there is a target dom in the current view and handle it accordingly
  • use

First, it has to be a component

jsx part

// FloatDiv.jsx
import { useState, useEffect } from 'react'

import './index.css'
// HOOK writing
function FloatDiv({ children }) {
  const [a, as] = useState([])
  useEffect(() => {
  }, [])

  return (
    <div className="FloatDiv_component">
      <div
        className="FloatDiv_component_aniBox"
      >
        {/* Page input data */}
        {children}
      </div>
    </div>
  )
}

export default FloatDiv

less part (vscode plug-in is used here to automatically convert. css files, so. css is introduced into. jsx)

The default sink is 50px fully transparent.

  • FloatDiv_component_aniClass is a class that needs to be added after sliding to the visual screen*
    transition is not added here because the delay time is alive and needs to be displayed in style. On the other hand, the sinking distance should be the same, that is, the transform:translate(l/r,t/b) in the css file should be pushed out at that time, so you need to set one in the transform in the appended class name! important to ensure that the hierarchy of this style is effective enough
.FloatDiv_component{
  width: auto;
  height: auto;
  position: relative;
  .FloatDiv_component_aniBox{
    opacity: 0;
    transform: translate(0, 50px);
  }
  .FloatDiv_component_aniClass{
    opacity: 1;
    transform: translate(0, 0)!important;
  }
}

Get all the target DOMS of the current page

// FloatDiv.jsx
import { useState, useEffect } from 'react'

import './index.css'

const floatDomClassName = '.FloatDiv_component .FloatDiv_component_aniBox'
let doms = []

// HOOK writing
function FloatDiv({ children }) {
  const [a, as] = useState([])
  useEffect(() => {
    // Convert the pseudo array into a real array so that it can use the method of array
    const d = Array.prototype.slice.call(
      document.querySelectorAll(floatDomClassName)
    )
    doms = d
    // Methods to be executed later
    init()
  }, [])
  // ...some code
 
}

export default FloatDiv

Judge whether there is a target dom in the current view and handle it accordingly

There are two ways to obtain the target DOM in the visual area, one is classic scroll bar listening, and the other is IntersectionObserver
Here, you can first judge whether the IntersectionObserver API is available, and then use the scroll bar listening method to implement this logic

The component also needs to open some custom parameters, such as delay and down

// ...some code
import PropTypes from 'prop-types'

/**
 * @param {Number} delay The default animation delay trigger time is 0s
 * @param {Number} down The floating distance of the box is 50px by default
 */

const floatDomClassName = '.FloatDiv_component .FloatDiv_component_aniBox'
// Throttling function
let _throttleFn
let doms = []

// The default value of the parameter is set here
function FloatDiv({ children, delay = 0, down = 50 }) {
  // eslint-disable-next-line no-unused-vars
  const [a, as] = useState([])
  useEffect(() => {
    // Get all the DOMS that need to be set, and convert the pseudo array into an array
    const d = Array.prototype.slice.call(
      document.querySelectorAll(floatDomClassName)
    )
    doms = d
    init()
  }, [])

  const init = () => {
    // When this API is available
    if ('IntersectionObserver' in window) {
      let floatDomObserver = new IntersectionObserver((entries) => {
        // Loop through each target
        entries.forEach((entry, index) => {
          // If the element is visible
          if (entry.isIntersecting) {
          	// Get target DOM
            let floatDom = entry.target
            // Update class name after a short delay
            const timer = setTimeout(() => {
              floatDom.className = 'box1 FloatDiv_component_aniClass'
              clearTimeout(timer)
            }, 300)
            // After the update, remove this DOM and no longer listen
            floatDomObserver.unobserve(floatDom)
            // Update external dom group
            doms.splice(index, 1)
          }
        })
      })
      // Facilitate doms to listen to each dom in turn
      doms.forEach((floatDomItem) => {
        floatDomObserver.observe(floatDomItem)
      })
    } else {
      // Default call first time
      inViewShow()
      // Set throttle
      _throttleFn = throttle(inViewShow)
      // Turn on listening
      document.addEventListener('scroll', _throttleFn.bind(this))
    }
  }

  const inViewShow = () => {
    let len = doms.length
    // Traverse each dom
    for (let i = 0; i < len; i++) {
      let targetFloatElement = doms[i]
      // Get current DOM viewport information
      const { top, bottom } = targetFloatElement.getBoundingClientRect()
      // Load the picture when it appears in the field of view
      // Because the top is calculated according to the sinking height after setting the sinking, if you want to be more accurate, you need to deduct the sinking distance (down) 
      // Then, in order to ensure that the execution of animation is faster than sliding, 10px is added as a guarantee (it can also be deleted, optional)
      // In addition, when scrolling from bottom to top, the guarantee box will be displayed only when it is slid to. Here, because the sinking emerges from bottom to top, no additional sinking value will be added
      if (
        top - down + 10 < document.documentElement.clientHeight &&
        bottom > 0
      ){
        const timer = setTimeout(() => {
          targetFloatElement.className = 'box1 FloatDiv_component_aniClass'
          clearTimeout(timer)
        }, 300)
        // Remove already displayed
        doms.splice(i, 1)
        len--
        i--
        if (doms.length === 0) {
          // If all are loaded, the scroll event listener is removed
          document.removeEventListener('scroll', _throttleFn)
        }
      }
    }
  }
  // Throttling function
  const throttle = (fn, delay = 100, mustRun = 30) => {
    let t_start = null
    let timer = null
    let context = this
    return function () {
      let t_current = +new Date()
      let args = Array.prototype.slice.call(arguments)
      clearTimeout(timer)
      if (!t_start) {
        t_start = t_current
      }
      if (t_current - t_start > mustRun) {
        fn.apply(context, args)
        t_start = t_current
      } else {
        timer = setTimeout(() => {
          fn.apply(context, args)
        }, delay)
      }
    }
  }
  return (
    <div className="FloatDiv_component">
      <div
        className="FloatDiv_component_aniBox"
        style={{
          transition: `all 1s ease ${delay}s`,
          transform: `translate(0, ${down}px)`
        }}
      >
        {/* Page input data */}
        {children}
      </div>
    </div>
  )
}
// eslint parameter type verification
FloatDiv.propTypes = {
  children: PropTypes.element,
  delay: PropTypes.number,
  down: PropTypes.number
}

use

Here, the development of components has been completed, and then the components are introduced into the page:

import FloatDiv from '@/components/FloatDiv'
function Page() {
    return (
    <div id="homePage_wrap">
      <FloatDiv>
        <div className="txt">This is a box aaa</div>
      </FloatDiv>
      <FloatDiv delay={1} down={20}>
        <div className="txt">This is a box b</div>
      </FloatDiv>
      <FloatDiv delay={2} down={30}>
        <div className="txt">This is a box c</div>
      </FloatDiv>
    </div>
  )
}

There is the animation effect at the top ~
·
·
·
·
·
·
·
·
Component overview:

import { useState, useEffect } from 'react'
import PropTypes from 'prop-types'

import './index.css'

/**
 * @param {Number} delay The default animation delay trigger time is 0s
 * @param {Number} down The floating distance of the box is 50px by default
 */

const floatDomClassName = '.FloatDiv_component .FloatDiv_component_aniBox'
let _throttleFn
let doms = []
// HOOK writing
function FloatDiv({ children, delay = 0, down = 50 }) {
  // eslint-disable-next-line no-unused-vars
  const [a, as] = useState([])
  useEffect(() => {
    // Get all the DOMS that need to be set, and convert the pseudo array into an array
    const d = Array.prototype.slice.call(
      document.querySelectorAll(floatDomClassName)
    )
    doms = d
    init()
  }, [])

  const init = () => {
    if ('IntersectionObserver' in window) {
      let floatDomObserver = new IntersectionObserver((entries) => {
        entries.forEach((entry, index) => {
          // If the element is visible
          if (entry.isIntersecting) {
            let floatDom = entry.target
            const timer = setTimeout(() => {
              floatDom.className = 'box1 FloatDiv_component_aniClass'
              clearTimeout(timer)
            }, 300)
            floatDomObserver.unobserve(floatDom)
            doms.splice(index, 1)
          }
        })
      })
      doms.forEach((floatDomItem) => {
        floatDomObserver.observe(floatDomItem)
      })
    } else {
      inViewShow()
      _throttleFn = throttle(inViewShow)
      document.addEventListener('scroll', _throttleFn.bind(this))
    }
  }

  const inViewShow = () => {
    let len = doms.length
    for (let i = 0; i < len; i++) {
      let targetFloatElement = doms[i]
      const { top, bottom } = targetFloatElement.getBoundingClientRect()
      // Load the picture when it appears in the field of view
      if (
        top - down + 10 < document.documentElement.clientHeight &&
        bottom >= 0
      ) {
        const timer = setTimeout(() => {
          targetFloatElement.className = 'box1 FloatDiv_component_aniClass'
          clearTimeout(timer)
        }, 300)
        // Remove already displayed
        doms.splice(i, 1)
        len--
        i--
        if (doms.length === 0) {
          // If all are loaded, the scroll event listener is removed
          document.removeEventListener('scroll', _throttleFn)
        }
      }
    }
  }
  const throttle = (fn, delay = 100, mustRun = 30) => {
    let t_start = null
    let timer = null
    let context = this
    return function () {
      let t_current = +new Date()
      let args = Array.prototype.slice.call(arguments)
      clearTimeout(timer)
      if (!t_start) {
        t_start = t_current
      }
      if (t_current - t_start > mustRun) {
        fn.apply(context, args)
        t_start = t_current
      } else {
        timer = setTimeout(() => {
          fn.apply(context, args)
        }, delay)
      }
    }
  }

  return (
    <div className="FloatDiv_component">
      <div
        className="FloatDiv_component_aniBox"
        style={{
          transition: `all 1s ease ${delay}s`,
          transform: `translate(0, ${down}px)`
        }}
      >
        {/* Page input data */}
        {children}
      </div>
    </div>
  )
}

FloatDiv.propTypes = {
  children: PropTypes.element,
  delay: PropTypes.number,
  down: PropTypes.number
}

export default FloatDiv

If reprinted, please indicate the original address and author!

Keywords: Javascript React css

Added by stoop on Fri, 15 Oct 2021 03:19:13 +0300