This article will introduce how to use native JS (mainly using ES6 syntax) to implement full screen scrolling plug-in, compatible with IE 10+, mobile touch screen, Mac trackpad optimization, support custom page animation, compressed gZIP file is only 2.15KB (including CSS files). The full source code is in this pure-full-page, click here to view the demo.
1) The previous words
There are plenty of full-screen scrolling plug-ins out there, like the famous fullPage, so why build your own wheels?
Existing wheels have the following problems:
- First, the biggest problem is that several of the most popular plugins rely on jQuery, which means using them in React or Vue projects is a pain in the ass: I only need a full-screen scroll feature, but I still need jQuery.
- Second, many of the existing full-screen scrolling plug-ins tend to be quite feature-rich, which was an advantage in previous years, but can now be seen as a disadvantage: Front-end development has changed a lot, one of the most important changes is that ES6 native support modular development, modular development is the biggest characteristic of a module is best to focus on one thing, and then put together a complete system, from this perspective, large and complete plug-ins are against the principle of modular development.
In contrast, building a wheel in a native language has the following benefits:
- Plug-ins written in native languages will not be affected by the usage scenarios of dependent plug-ins (plug-ins that rely on jQuery are not suitable for single-page application development), so they are more flexible in use.
- With modular development, plug-ins developed in native languages can focus on only one function, so the amount of code can be minimal.
- Finally, with JS/CSS/HTML and browser iterations, it’s getting cheaper to develop plug-ins in native languages, so why not?
2) Implementation principle and code architecture
2.1 Implementation Principles
The realization principle is shown in the following figure: the height of the container and the page in the container is the current viewable area, and the overflow property value of the container’s parent element is set to hidden, and the full-screen scrolling effect is realized by changing the value of the container top.
2.2 Code Architecture
The idea behind the code is to define a full-screen scrolling class through class, which is used through new PureFullPage().init().
/** * full screen scroll class */
class PureFullPage {
// constructor
constructor() {}
// Prototype method
methods() {}
// Initialize the function
init() {}
}
Copy the code
3) HTML structure
The page container is #pureFullPageContainer, and all pages are its direct child elements. For convenience, body is the direct parent element.
<body>
<div id="pureFullPageContainer">
<div class="page"></div>
<div class="page"></div>
<div class="page"></div>
</div>
</body>
Copy the code
4) CSS Settings
First, the container and the pages in the container take the height of the current visual area, in preparation for each switch to display a complete page;
Second, the overflow property of the container’s parent element (in this case, body) is set to hidden, so that only one page is displayed at a time and the rest of the pages are hidden.
After the above Settings, the container top value, each change a visual area height distance, then realized the switch between pages, part of the code is as follows:
body {
/* Body is the direct parent of the container */
overflow: hidden;
}
#pureFullPage {
/* The top value is valid only if the value of position is not static */
position: relative;
/* Sets the initial value */
top: 0;
}
.page {
/* Cannot be 100vh, more on */ later
/* The parent element, the height of #pureFullPage, is set dynamically with js */
height: 100%;
}
Copy the code
Notice:
-
The position property of the container should be set to relative because top is valid only if the position property is not static.
-
The page height should be set to the current viewable area, but not to 100vh because safari takes the address bar into account, but the area below the address bar should not count as “viewable area” because it is actually “invisible”. This will result in 100 vh corresponding pixel values than the document. The documentElement. ClientHeight get pixel values. Instead of switching the top value in full screen, the height of the switch is actually less than the height of the page.
-
Fixed safari mobile browser viewable height issue: Since by js to get the document. The documentElement. ClientHeight value is in line with expectations of visual zone height (not including the top and bottom address bar bar), then the value by js set to the height of the container, at the same time, the page height of the container is set to 100%, This ensures that the height of the container and the page are the same as the top value of the switch, thus ensuring a full screen switch.
/ / pseudo code
'#pureFullPage'.style.height = document.documentElement.clientHeight + 'px';
Copy the code
5) Monitor scroll/slide events
The scroll/swipe events here include mouse scrolling, touchpad sliding, and swiping up and down the phone screen.
5.1 PC
The main problem solved on PC is to obtain the direction of mouse scrolling or trackpad sliding. The trackpad sliding up and down and mouse scrolling are bound to the same event:
- Firefox is
DOMMouseScroll
Event, the corresponding wheel information (roll forward or roll backward) is stored indetail
Property, roll forward, the value of this property is a multiple of 3, and vice versa, is a multiple of -3; - Other browsers than Firefox are
mousewheel
Event corresponding to the wheel information stored inwheelDelta
Property, roll forward, this property value is a multiple of -120, and vice versa, a multiple of 120.
MacOS, not Windows?
So, you can control whether the page scrolls up or down by determining the scrolling direction of the mouse from the value of detail or wheelDelta. Here we only care about the positive and negative values, but for ease of use, we wrap a function based on these two events: if the mouse scrolls forward, it returns a negative number, and vice versa, it returns a positive number, as follows:
// Mouse wheel event
getWheelDelta(event) {
if (event.wheelDelta) {
return event.wheelDelta;
} else {
// Compatible with Firefox
return-event.detail; }},Copy the code
With scroll events, you can write callbacks that scroll up or down the page as follows:
// Mouse scrolling logic (full screen scrolling key logic)
scrollMouse(event) {
let delta = utils.getWheelDelta(event);
// if delta < 0, scroll down
if (delta < 0) {
this.goDown();
} else {
this.goUp(); }}Copy the code
GoDown and goUp are logical codes for page scrolling. It should be noted that the scrolling boundary must be determined to ensure that the page content is always displayed in the container:
- The top edge is easily defined as the height of a page (or viewport), if the current top edge of the container is the distance from the top of the entire page (here this value is the container’s)
offsetTop
The absolute value of the value because of its parent elementoffsetTop
Values are0
) is greater than or equal to the height of the current visual area, is allowed to scroll up, otherwise, it proves that there is no page above, not allowed to continue to scroll up; - Under the boundary
n - 2
(n indicates the number of pages scrolling in full screen) the height of the viewable area when the container’soffsetTop
The absolute value of the value is less than or equal ton - 2
Is the height of the viewable area, which means you can scroll down one more page.
The specific code is as follows:
goUp() {
// The page scrolls up when only the top of the page remains
if (-this.container.offsetTop >= this.viewHeight) {
// Respecify currentPosition, the distance between the current page and the top of the view, to achieve full-screen scrolling,
// currentPosition is negative, larger means less over the top
this.currentPosition = this.currentPosition + this.viewHeight;
this.turnPage(this.currentPosition);
}
}
goDown() {
// Scroll down only when there is a page at the bottom of the page
if (-this.container.offsetTop <= this.viewHeight * (this.pagesNum - 2)) {
// Respecify currentPosition, the distance between the current page and the top of the view, to achieve full-screen scrolling,
// currentPosition is negative, smaller means more over the top
this.currentPosition = this.currentPosition - this.viewHeight;
this.turnPage(this.currentPosition); }}Copy the code
Finally add the scroll event:
// Mouse wheel listener, Firefox mouse scroll events are different
if (navigator.userAgent.toLowerCase().indexOf('firefox') = = =- 1) {
document.addEventListener('mousewheel', scrollMouse);
} else {
document.addEventListener('DOMMouseScroll', scrollMouse);
}
Copy the code
5.2 the mobile end
To determine whether to swipe up or down, the mobile terminal can combine two events, touchStart (triggered when the finger begins to touch the screen) and Touchend (triggered when the finger leaves the screen) : Get the pageY value when the two events are triggered at the beginning. If the pageY at the end of touch is greater than the pageY at the beginning of touch, it means that the finger slides down and the corresponding page scrolls up, and vice versa.
Here we need the touch event to track the properties of the touch:
touches
: An array of Touch objects for the current traced Touch operations, used to get the start of the TouchpageY
Value;changeTouches
: Array of Touch objects that have changed since the last Touch, used to get the end of the Touch TouchpageY
Value.
The relevant codes are as follows:
// Touch the screen with your finger
document.addEventListener('touchstart', event => {
this.startY = event.touches[0].pageY;
});
// Take your finger off the screen
document.addEventListener('touchend', event => {
let endY = event.changedTouches[0].pageY;
if (endY - this.startY < 0) {
// Swipe up to scroll down the page
this.goDown();
} else {
// Swipe down to scroll up
this.goUp(); }});Copy the code
To avoid a pull-down refresh, you can prevent the default behavior of the TouchMove event:
// Prevent the TouchMove pull-down refresh
document.addEventListener('touchmove', event => {
event.preventDefault();
});
Copy the code
6) PC side rolling event performance optimization
6.1 Describes the Anti-vibration function and current closure function
Optimization mainly starts from two conveniences:
- When changing the size of a page, limit it with the debounce function
resize
Event triggering frequency; - When scroll/slide events are triggered, throttle is used to limit the frequency of scroll/slide events.
What’s the difference between a timer and a timer since they both limit the trigger frequency?
First, when the anti-jitter function is working, if an event is triggered continuously within the specified delay time, the callback function bound to the event will never be triggered. Only when the event is not triggered again within the specified delay time, the corresponding callback function will be executed. The anti-jitter function is very suitable for changing the size of the window. It also conforms to the intuition that the event will be triggered after the drag is in place. If the drag keeps on, the event will not be triggered.
Unlike a intercepting function, which fires only once and during the delay time, a callback bound to the event does not prevent a callback from executing during the delay time, even if the event is continuously fired during the delay time. And the intercepting function allows us to specify whether the callback is executed at the beginning or end of the delay time.
Because of the above two characteristics of the closure function, it is particularly suitable for optimizing scroll/slide events:
- You can limit the frequency;
- No callbacks registered on the event cannot be executed because the scroll/slide event is too sensitive (repeatedly triggered within the delay time);
- The callback function can be set to trigger when the delay time starts to avoid the user feeling a short delay after the operation.
Here we don’t explain the principles of the anti-jitter functions and intercepting functions, but to our interest you can read Throttling and Debouncing in JavaScript, and the following code implements them:
// Anti-jitter function, method callback function, context context, event incoming time, delay delay function
debounce(method, context, event, delay) {
clearTimeout(method.tId);
method.tId = setTimeout((a)= > {
method.call(context, event);
}, delay);
},
// Method callback, context context, delay function,
// There is no option to execute the callback at the beginning or end of the delay,
// Execute the callback directly when the delay time starts
throttle(method, context, delay) {
let wait = false;
return function() {
if(! wait) { method.apply(context,arguments);
wait = true;
setTimeout((a)= > {
wait = false; }, delay); }}; },Copy the code
The throttle function described in section 22.33.3 of JavaScript Advanced Programming – 3rd Edition is not the same as the debounce function described here, but most articles on the web are not. For example, debounce is defined in Lodash.
6.2 Modifying the Rolling Event on the PC
We already know that intercepting functions can improve performance by limiting the frequency with which scroll events are triggered, and that setting the callback function to call scroll events immediately at the start of the delay time does not sacrifice the user experience.
The intercepting function has been defined above and is simple to use:
// Set the intercepting function
let handleMouseWheel = utils.throttle(this.scrollMouse, this.this.DELAY, true);
// Mouse wheel listener, Firefox mouse scroll events are different
if (navigator.userAgent.toLowerCase().indexOf('firefox') = = =- 1) {
document.addEventListener('mousewheel', handleMouseWheel);
} else {
document.addEventListener('DOMMouseScroll', handleMouseWheel);
}
Copy the code
The above code is written in the init method of the class, so the intercepting function’s context is passed this, representing the current class instance.
7) other
7.1 Navigation Buttons
To simplify HTML structure, navigation buttons are created using JS. The difficulty here lies in how to click different buttons to realize the corresponding page jump and update the corresponding button style.
The solution is:
- Page jump: The number of pages is the same as the number of navigation buttons, so clicking the i-th button will jump to the i-th page, and the container corresponding to the i-th page
top
Value happens to be-(i * this.viewHeight)
- Change style: To change style, first remove the selected style of all buttons and then add the selected style to the currently clicked button.
// Create a right point navigation
createNav() {
const nav = document.createElement('div');
nav.className = 'nav';
this.container.appendChild(nav);
// There are several pages, showing several dots
for (let i = 0; i < this.pagesNum; i++) {
nav.innerHTML += '<p class="nav-dot"><span></span></p>';
}
const navDots = document.querySelectorAll('.nav-dot');
this.navDots = Array.prototype.slice.call(navDots);
// Add an initial style
this.navDots[0].classList.add('active');
// Add dot navigation click events
this.navDots.forEach((el, i) = > {
el.addEventListener('click', event => {
// Page jump
this.currentPosition = -(i * this.viewHeight);
this.turnPage(this.currentPosition);
// Change the style
this.navDots.forEach(el= > {
utils.deleteClassName(el, 'active');
});
event.target.classList.add('active');
});
});
}
Copy the code
7.2 User-defined Parameters
Proper customization of parameters adds flexibility to the plug-in.
Parameters are passed in via constructors and are merged with object.assign () :
constructor(options) {
// Default configuration
const defaultOptions = {
isShowNav: true.delay: 150.definePages: (a)= >{}};// Merge custom configurations
this.options = Object.assign(defaultOptions, options);
}
Copy the code
7.3 Update data when the window size changes
When the browser window size changes, you need to re-capture the viewable area, page element height, and redetermine the current top value of the container.
Also, to avoid unnecessary performance costs, anti-jitter functions are used.
// Retrieve the position when window resize
getNewPosition() {
this.viewHeight = document.documentElement.clientHeight;
this.container.style.height = this.viewHeight + 'px';
let activeNavIndex;
this.navDots.forEach((e, i) = > {
if (e.classList.contains('active')) { activeNavIndex = i; }});this.currentPosition = -(activeNavIndex * this.viewHeight);
this.turnPage(this.currentPosition);
}
handleWindowResize(event) {
// Set the anti-jitter function
utils.debounce(this.getNewPosition, this, event, this.DELAY);
}
// Resets the position when the window size changes
window.addEventListener('resize'.this.handleWindowResize.bind(this));
Copy the code
7.4 compatibility
Compatibility here mainly refers to two aspects: one is that different browsers define different apis for the same behavior, for example, the API for obtaining mouse scroll information mentioned above is different from other browsers; The second point is ES6 new syntax, new API compatibility processing.
The conversion of new syntax, such as class and arrow functions, can be completed through Babel. In view of the small amount of code in this plug-in, it is in a controlled state, and does not introduce the polyfill scheme provided by Babel, because the new API only needs to be compatible with object.assign (). Just write a separate polyfill as follows:
// polyfill Object.assign
polyfill() {
if (typeof Object.assign ! ='function') {
Object.defineProperty(Object.'assign', {
value: function assign(target, varArgs) {
if (target == null) {
throw new TypeError('Cannot convert undefined or null to object');
}
let to = Object(target);
for (let index = 1; index < arguments.length; index++) {
let nextSource = arguments[index];
if(nextSource ! =null) {
for (let nextKey in nextSource) {
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) { to[nextKey] = nextSource[nextKey]; }}}}return to;
},
writable: true.configurable: true}); }},Copy the code
From: mdN-object.assign ()
Since this plugin is only compatible with IE10, we do not intend to do compatibility handling for events, since IE9 already supports AddeventListeners.
7.5 Further optimize performance with lazy loading
The getWheelDelta function written in 5.1 needs to check for event.wheelDelta support every time it is executed. In fact, the browser only needs to check for event.
And this check is performed many times over the life of the page, in which case it can be optimized with lazy loading techniques like the following:
getWheelDelta(event) {
if (event.wheelDelta) {
// Load lazily after the first call without checking
this.getWheelDelta = event= > event.wheelDelta;
// the first call is used
return event.wheelDelta;
} else {
// Compatible with Firefox
this.getWheelDelta = event= > -event.detail;
return-event.detail; }},Copy the code
The full source code is in this pure-full-page, click here to view the demo.
The resources
Throttling and Debouncing in JavaScript debttling and Throttling Explained Through Examples JavaScript Debounce Function Simple throttle in js Simple throttle in js – jsfiddle Viewport height is taller than the visible Part of the document in some mobile Browsers MDN-object. Assign () Babel compiled or ES 6? Do you have to go to Polyfill? – Henry’s answer