ES6实现全屏滚动插件步骤详解
这次给大家带来ES6实现全屏滚动插件步骤详解,ES6实现全屏滚动插件使用的注意事项有哪些,下面就是实战案例,一起来看一下。
1)前面的话
现在已经有很多全屏滚动插件了,比如著名的 fullPage ,那为什么还要自己造轮子呢?
现有轮子有以下问题:
首先,最大的问题是最流行的几个插件都依赖 jQuery,这意味着在使用 React 或者 Vue 的项目中使用他们是一件十分蛋疼的事:我只需要一个全屏滚动功能,却还需要把 jQuery 引入,有种杀鸡使用宰牛刀的感觉;
其次,现有的很多全屏滚动插件功能往往都十分丰富,这在前几年是优势,但现在(2018-5)可以看作是劣势:前端开发已经发生了很大变化,其中很重要的一个变化是 ES6 原生支持模块化开发,模块化开发最大的特点是一个模块最好只专注做好一件事,然后再拼成一个完整的系统,从这个角度看,大而全的插件有悖模块化开发的原则。
对比之下,通过原生语言造轮子有以下好处:
使用原生语言编写的插件,自身不会受依赖的插件的使用场景而影响自身的使用(现在依赖 jQuery 的插件非常不适合开发单页面应用),所以使用上更加灵活;
搭配模块化开发,使用原生语言开发的插件可以只专注一个功能,所以代码量可以很少;
最后,随着 JS/CSS/HTML 的发展以及浏览器不断迭代更新,现在使用原生语言编写插件的开发成本越来越低,那为什么不呢?
2)实现原理及代码架构
2.1 实现原理
实现原理见下图:容器及容器内的页面取当前可视区高度,同时容器的父级元素 overflow 属性值设为 hidden ,通过更改容器 top 值实现全屏滚动效果。
2.2 代码架构
代码编写的思路是通过 class 定义全屏滚动类,使用时通过 new PureFullPage().init()
使用。
/** * 全屏滚动类 */ class PureFullPage { // 构造函数 constructor() {} // 原型方法 methods() {} // 初始化函数 init() {} }
3)html 结构
鉴于上述实现原理,对于 html 的结构有特定要求,如下:页面容器为 #pureFullPageContainer ,所有的页面为其直接子元素,这里为了方便,直接取 body 为其直接父元素。
<body> <p id="pureFullPageContainer"> <p class="page"></p> <p class="page"></p> <p class="page"></p> </p> </body>
4)css 设置
首先,容器及容器内的页面取当前可视区高度,为每次切换都显示一个完整的页面做准备;
第二,容器的父级元素(此处是 body ) overflow 属性值定为 hidden ,这样可以保证每次只会显示一个页面,其他页面被隐藏。
经过上述设置,对容器 top 值,每次更改一个可视区高度的距离,便实现了页面间的切换,部分代码如下:
body { /* body 为容器直接的父元素 */ overflow: hidden; } #pureFullPage { /* 只有当 position 的值不是 static 时,top 值才有效 */ position: relative; /* 设置初始值 */ top: 0; } .page { /* 此处不能为 100vh,后面详述 */ /* 其父元素,也就是 #pureFullPage 的高度,通过 js 动态设置*/ height: 100%; }
Notice:
容器的 position 属性值需要设置为 relative ,因为 top 只有在 position 属性值不为 static 时才有效;
页面高度需设置为当前可视区高度,但不能直接设置为 100vh ,因为 safari 手机浏览器把地址栏算进去计算 100vh ,但地址栏下面的不应该算做“可视区”,毕竟实际上是“看不见”的区域。这会导致 100vh 对应的像素值比 document.documentElement.clientHeight 获取的像素值大。这样在切换 top 值时就不是全屏切换了,实际上,这种情况下切换的高度小于页面的高度。
解决 safari 手机浏览器可视区高度问题:既然通过 js 获取的 document.documentElement.clientHeight 值是符合预期的可视区高度(不包括顶部地址栏和底部工具栏),那就 将该值通过 js 设置为容器的高度,同时,容器内的页面高度设置为 100% ,这样就可以保证容器及页面的高度和切换 top 值相同了,也就保证了全屏切换。
// 伪代码 '#pureFullPage'.style.height = document.documentElement.clientHeight + 'px';
5)监控滚动/滑动事件
这里的滚动/滑动事件包括鼠标滚动、触摸板滑动以及手机屏幕上下滑动。
5.1 PC 端
PC 端主要解决的问题是获取鼠标滚动或触摸板滑动方向,触摸板上下滑动和鼠标滚动绑定的是同一个事件:
firefox 是 DOMMouseScroll 事件,对应的滚轮信息(向前滚还是向后滚)存储在 detail 属性中,向前滚,这个属性值是 3 的倍数,反之,是 -3 的倍数;
firefox 之外的其他浏览器是 mousewheel 事件,对应的滚轮信息存储在 wheelDelta 属性中,向前滚,这个属性值是 -120 的倍数,反之, 120 的倍数。
macOS 如此,windows 相反?
所以,可以通过 detail 或 wheelDelta 的值判断鼠标的滚动方向,进而控制页面是向上还是向下滚动。在这里我们只关心正负,不关心具体值的大小,为了便于使用,下面基于这两个事件封装了一个函数:如果鼠标往前滚动,返回负数,反之,返回正数,代码如下:
// 鼠标滚轮事件 getWheelDelta(event) { if (event.wheelDelta) { return event.wheelDelta; } else { // 兼容火狐 return -event.detail; } },
有了滚动事件,就可以据此编写页面向上或者向下滚动的回调函数了,如下:
// 鼠标滚动逻辑(全屏滚动关键逻辑) scrollMouse(event) { let delta = utils.getWheelDelta(event); // delta < 0,鼠标往前滚动,页面向下滚动 if (delta < 0) { this.goDown(); } else { this.goUp(); } }
goDown 、 goUp 是页面滚动的逻辑代码,需要特别说明的是必须 判断滚动边界,保证容器中显示的始终是页面内容 :
上边界容易确定,为 1 个页面(也即可视区)的高度,即如果容器当前的上外边框距离整个页面顶部的距离(这里此值正是容器的 offsetTop 值的绝对值,因为它父元素的 offsetTop 值都是 0 )大于等于当前可视区高度时,才允许向上滚动,不然,就证明上面已经没有页面了,不允许继续向上滚动;
下边界为 n - 2 (n 表示全屏滚动的页面数) 个可视区的高度,当容器的 offsetTop 值的绝对值小于等于 n - 2 个可视区的高度时,表示还可以向下滚动一个页面。
具体代码如下:
goUp() { // 只有页面顶部还有页面时页面向上滚动 if (-this.container.offsetTop >= this.viewHeight) { // 重新指定当前页面距视图顶部的距离 currentPosition,实现全屏滚动, // currentPosition 为负值,越大表示超出顶部部分越少 this.currentPosition = this.currentPosition + this.viewHeight; this.turnPage(this.currentPosition); } } goDown() { // 只有页面底部还有页面时页面向下滚动 if (-this.container.offsetTop <= this.viewHeight * (this.pagesNum - 2)) { // 重新指定当前页面距视图顶部的距离 currentPosition,实现全屏滚动, // currentPosition 为负值,越小表示超出顶部部分越多 this.currentPosition = this.currentPosition - this.viewHeight; this.turnPage(this.currentPosition); } }
最后添加滚动事件:
// 鼠标滚轮监听,火狐鼠标滚动事件不同其他 if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) { document.addEventListener('mousewheel', scrollMouse); } else { document.addEventListener('DOMMouseScroll', scrollMouse); }
5.2 移动端
移动端需要判断是向上还是向下滑动,可以结合 touchstart (手指开始接触屏幕时触发) 和 touchend (手指离开屏幕时触发) 两个事件实现判断:分别获取两个事件开始触发时的 pageY 值,如果触摸结束时的 pageY 大于触摸开始时的 pageY ,表示手指向下滑动,对应页面向上滚动,反之亦然。
此处我们需要触摸事件跟踪触摸的属性:
touches :当前跟踪的触摸操作的 Touch 对象的数组,用于获取触摸开始时的 pageY 值;
changeTouches :自上次触摸以来发生了改变的 Touch 对象的数组,用于获取触摸触摸结束时的 pageY 值。
相关代码如下:
// 手指接触屏幕 document.addEventListener('touchstart', event => { this.startY = event.touches[0].pageY; }); //手指离开屏幕 document.addEventListener('touchend', event => { let endY = event.changedTouches[0].pageY; if (endY - this.startY < 0) { // 手指向上滑动,对应页面向下滚动 this.goDown(); } else { // 手指向下滑动,对应页面向上滚动 this.goUp(); } });
为了避免下拉刷新,可以阻止 touchmove 事件的默认行为:
// 阻止 touchmove 下拉刷新 document.addEventListener('touchmove', event => { event.preventDefault(); });
6)PC 端滚动事件性能优化
6.1 防抖函数和截流函数介绍
优化主要从两方便入手:
更改页面大小时,通过防抖动(debounce)函数限制 resize 事件触发频率;
滚动/滑动事件触发时,通过截流(throttle)函数限制滚动/滑动事件触发频率。
既然都是限制触发频率(都通过定时器实现),那这两者有什么区别?
首先,防抖动函数工作时,如果在指定的延迟时间内,某个事件连续触发,那么绑定在这个事件上的回调函数永远不会触发,只有在延迟时间内,这个事件没再触发,对应的回调函数才会执行。防抖动函数非常适合改变窗口大小这一事件,这也符合 拖动到位以后再触发事件,如果一直拖个不停,始终不触发事件 这一直觉。
而截流函数是在延迟时间内,绑定到事件上的回调函数能且只能触发一次,这和截流函数不同,即便是在延迟时间内连续触发事件,也不会阻止在延迟时间内有一个回调函数执行。并且截流函数允许我们指定回调函数是在延迟时间开始时还是结束时执行。
鉴于截流函数的上述两个特性,尤其适合优化滚动/滑动事件:
可以限制频率;
不会因为滚动/滑动事件太灵敏(在延迟时间内不断触发)导致注册在事件上的回调函数无法执行;
可以设置在延迟时间开始时触发回调函数,从而避免用户感到操作之后的短暂延时。
这里不介绍防抖动函数和截流函数的实现原理,感兴趣的可以看 Throttling and Debouncing in JavaScript ,下面是实现的代码:
// 防抖动函数,method 回调函数,context 上下文,event 传入的时间,delay 延迟函数 debounce(method, context, event, delay) { clearTimeout(method.tId); method.tId = setTimeout(() => { method.call(context, event); }, delay); }, // 截流函数,method 回调函数,context 上下文,delay 延迟函数, // immediate 传入 true 表示在 delay 开始时执行回调函数 throttle(method, context, delay, immediate) { return function() { const args = arguments; const later = () => { method.tID = null; if (!immediate) { method.apply(context, args); } }; const callNow = immediate && !method.tID; clearTimeout(method.tID); method.tID = setTimeout(later, delay); if (callNow) { method.apply(context, args); } }; },
《JavaScript 高级程序设计 - 第三版》 22.33.3 节中介绍的 throttle 函数和此处定义的不同,高程中定义的 throttle 函数对应此处的 debounce 函数,但网上大多数文章都和高程中的不同,比如 lodash 中定义的 debounce 。
6.2 改造 PC 端滚动事件
通过上述说明,我们已经知道截流函数可以通过限定滚动事件触发频率提升性能,同时,设置在 延迟时间开始阶段立即调用滚动事件的回调函数 并不会牺牲用户体验。
截流函数上文已经定义好,使用起来就很简单了:
// 设置截流函数 let handleMouseWheel = utils.throttle(this.scrollMouse, this, this.DELAY, true); // 鼠标滚轮监听,火狐鼠标滚动事件不同其他 if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) { document.addEventListener('mousewheel', handleMouseWheel); } else { document.addEventListener('DOMMouseScroll', handleMouseWheel); }
上面这部分代码是写在 class 的 init 方法中,所以截流函数的上下文(context)传入的是 this ,表示当前 class 实例。
7)其他
7.1 导航按钮
为了简化 html 结构,导航按钮通过 js 创建。这里的难点在于 如何实现点击不同按钮实现对应页面的跳转并更新对应按钮的样式 。
解决的思路是:
页面跳转:页面个数和导航按钮的个数一致,所以点击第 i 个按钮也就是跳转到第 i 个页面,而第 i 个页面对应的容器 top 值恰好是 -(i * this.viewHeight)
更改样式:更改样式即先删除所有按钮的选中样式,然后给当前点击的按钮添加选中样式。
// 创建右侧点式导航 createNav() { const nav = document.createElement('p'); nav.className = 'nav'; this.container.appendChild(nav); // 有几页,显示几个点 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); // 添加初始样式 this.navDots[0].classList.add('active'); // 添加点式导航点击事件 this.navDots.forEach((el, i) => { el.addEventListener('click', event => { // 页面跳转 this.currentPosition = -(i * this.viewHeight); this.turnPage(this.currentPosition); // 更改样式 this.navDots.forEach(el => { utils.deleteClassName(el, 'active'); }); event.target.classList.add('active'); }); }); }
7.2 自定义参数
得当的自定义参数可以增加插件的灵活性。
参数通过构造函数传入,并通过 Object.assign() 进行参数合并:
constructor(options) { // 默认配置 const defaultOptions = { isShowNav: true, delay: 150, definePages: () => {}, }; // 合并自定义配置 this.options = Object.assign(defaultOptions, options); }
7.3 窗口尺寸改变时更新数据
浏览器窗口尺寸改变的时候,需要重新获取可视区、页面元素高度,并重新确定容器当前的 top 值。
同时,为了避免不必要的性能开支,这里使用了防抖动函数。
// 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) { // 设置防抖动函数 utils.debounce(this.getNewPosition, this, event, this.DELAY); } // 窗口尺寸变化时重置位置 window.addEventListener('resize', this.handleWindowResize.bind(this));
7.4 兼容性
这里的兼容性主要指两个方面:一是不同浏览器对同一行为定义了不同 API,比如上文提到的获取鼠标滚动信息的 API Firefox 和其他浏览器不一样;第二点就是 ES6 新语法、新 API 的兼容处理。
对于 class、箭头函数这类新语法的转换,通过 babel 就可完成,鉴于本插件代码量很小,都处于可控的状态,并没有引入 babel 提供的 polyfill 方案,因为新 API 只有 Object.assign() 需要做兼容处理,单独写个 polyfill 就好,如下:
// 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, }); } },
引用自: MDN-Object.assign()
因为本插件只兼容到 IE10,所以不打算对事件做兼容处理,毕竟IE9 都支持 addEventListener 了。
7.5 通过惰性载入进一步优化性能
在 5.1 中写的 getWheelDelta 函数每次执行都需要检测是否支持 event.wheelDelta ,实际上,浏览器只需在第一次加载时检测,如果支持,接下来都会支持,再做检测是没必要的。
并且这个检测在页面的生命周期中会执行很多次,这种情况下可以通过 惰性载入 技巧进行优化,如下:
getWheelDelta(event) { if (event.wheelDelta) { // 第一次调用之后惰性载入,无需再做检测 this.getWheelDelta = event => event.wheelDelta; // 第一次调用使用 return event.wheelDelta; } else { // 兼容火狐 this.getWheelDelta = event => -event.detail; return -event.detail; } },
相信看了本文案例你已经掌握了方法,更多精彩请关注php中文网其它相关文章!
推荐阅读:
以上是ES6实现全屏滚动插件步骤详解的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

AI Hentai Generator
免费生成ai无尽的。

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

热门话题

iPhone上的默认地图是Apple专有的地理位置提供商“地图”。尽管地图越来越好,但它在美国以外的地区运行不佳。与谷歌地图相比,它没有什么可提供的。在本文中,我们讨论了使用Google地图成为iPhone上的默认地图的可行性步骤。如何在iPhone中使Google地图成为默认地图将Google地图设置为手机上的默认地图应用程序比您想象的要容易。请按照以下步骤操作–先决条件步骤–您必须在手机上安装Gmail。步骤1–打开AppStore。步骤2–搜索“Gmail”。步骤3–点击Gmail应用旁

不断推出新版本以提供更好的使用体验,微信作为中国的社交媒体平台之一。升级微信至最新版本是非常重要的,家人和同事的联系、为了保持与朋友、及时了解最新动态。1.了解最新版本的特性与改进了解最新版本的特性与改进非常重要,在升级微信之前。性能改进和错误修复,通过查看微信官方网站或应用商店中的更新说明、你可以了解到新版本所带来的各种新功能。2.检查当前微信版本我们需要检查当前手机上已安装的微信版本、在升级微信之前。点击,打开微信应用“我”然后选择,菜单“关于”在这里你可以看到当前微信的版本号,。3.打开应

使用AppleID登录iTunesStore时,可能会在屏幕上抛出此错误提示“此AppleID尚未在iTunesStore中使用”。没有什么可担心的错误提示,您可以按照这些解决方案集进行修复。修复1–更改送货地址此提示出现在iTunesStore中的主要原因是您的AppleID个人资料中没有正确的地址。步骤1–首先,打开iPhone上的iPhone设置。步骤2–AppleID应位于所有其他设置的顶部。所以,打开它。步骤3–在那里,打开“付款和运输”选项。步骤4–使用面容ID验证您的访问权限。步骤

PyCharm是一款功能强大且受欢迎的Python集成开发环境(IDE),提供了丰富的功能和工具,使得开发者们可以更加高效地编写代码。而PyCharm的插件机制更是其功能扩展的利器,通过安装不同的插件,可以为PyCharm增加各种功能和定制化的特性。因此,对于PyCharm新手来说,了解并熟练安装插件是至关重要的。本文将为你详细介绍PyCharm插件安装的全

iPhone上的Shazam应用程序有问题?Shazam可帮助您通过聆听歌曲找到歌曲。但是,如果Shazam无法正常工作或无法识别歌曲,则必须手动对其进行故障排除。修复Shazam应用程序不会花费很长时间。因此,无需再浪费时间,请按照以下步骤解决Shazam应用程序的问题。修复1–禁用粗体文本功能iPhone上的粗体文本可能是Shazam无法正常运行的原因。步骤1–您只能从iPhone设置中执行此操作。所以,打开它。步骤2–接下来,打开其中的“显示和亮度”设置。步骤3–如果您发现启用了“粗体文本

Windows11作为微软最新推出的操作系统,深受广大用户喜爱。在使用Windows11的过程中,有时候我们需要获取系统管理员权限,以便进行一些需要权限的操作。接下来将详细介绍在Windows11中获取系统管理员权限的步骤。第一步,点击“开始菜单”,在左下角可以看到Windows图标,点击该图标便可打开“开始菜单”。第二步,在“开始菜单”中寻找并点击“

屏幕截图功能在您的iPhone上不起作用吗?截屏非常简单,因为您只需同时按住“提高音量”按钮和“电源”按钮即可抓取手机屏幕。但是,还有其他方法可以在设备上捕获帧。修复1–使用辅助触摸使用辅助触摸功能截取屏幕截图。步骤1–转到您的手机设置。步骤2–接下来,点击以打开“辅助功能”设置。步骤3–打开“触摸”设置。步骤4–接下来,打开“辅助触摸”设置。步骤5–打开手机上的“辅助触摸”。步骤6–打开“自定义顶级菜单”以访问它。步骤7–现在,您只需将这些功能中的任何一个链接到屏幕捕获即可。因此,点击那里的首

如果您无法控制Safari中的缩放级别,完成工作可能会非常棘手。因此,如果Safari看起来被缩小了,那对您来说可能会有问题。您可以通过以下几种方法解决Safari中的这个缩小小问题。1.光标放大:在Safari菜单栏中选择“显示”>“放大光标”。这将使光标在屏幕上更加显眼,从而更容易控制。2.移动鼠标:这可能听起来很简单,但有时只需将鼠标移动到屏幕上的另一个位置,可能会自动恢复正常大小。3.使用键盘快捷键修复1–重置缩放级别您可以直接从Safari浏览器控制缩放级别。步骤1–当您在Safari
