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