www.2527.com_澳门新葡8455手机版_新京葡娱乐场网址_
做最好的网站

令人着迷滚动实现指南,前端埋点之曝光实现

2020-02-16 18:52 来源:未知

时间: 2020-01-01阅读: 69标签: 埋点

背景

一些滚动的效果是如此令人着迷但你却不知该如何实现,本文将为你揭开它们的神秘面纱。我们将基于最新的技术与规范为你介绍最新的 JavaScript 与 CSS 特性,将使你的页面滚动更平滑、美观且性能更好。

最近有一个工作需求是曝光埋点,让我得以有机会接触相关的东西。之前实习时没有做过这方面的需求,个人项目更是和埋点扯不上关系。以至于上周开会讨论时听到“埋点”这个词就怂了。

最近项目中,开发一个小程序列表页,PM大大给我提了一个埋点需求,列表中的每一项,出现在屏幕中的时候,需要上报一条记录。

大多数的网页的内容都无法在一屏内全部展现,因而滚动对于用户而言是必不可少的。对于前端工程师与 UX 设计师而言,跨浏览器提供良好的滚动体验,同时符合设计,无疑是一个挑战。尽管 web 标准的发展速度远超从前,但代码的实现往往是落后的。下文将为你介绍一些常见的关于滚动的案例,检查一下你所用的解决方案是否被更优雅的方案所代替。

不过后面听大佬分析了下后才意识到,原来“埋点”是这个意思。曝光埋点的思路也是很简单:无非是判断某个DOM是否出现在视窗中,出现了就收集数据上报给服务端。

目标

这里推荐一下我的学习交流群:731771211,里面都是学习前端的,如果你想制作酷炫的网页,想学习编程。从最基础的HTML CSS JS【炫酷特效,游戏,插件封装,设计模式】到移动端HTML5的项目实战的学习资料都有整理,送给每一位前端小伙伴,有想学习web前端的,或是转行,或是大学生,还有工作中想提升自己能力的,正在学习的小伙伴欢迎加入。

所谓“埋点”,是数据采集领域(尤其是用户行为数据采集领域)的术语,指的是针对特定用户行为或事件进行捕获、处理和发送的相关技术及其实施过程。比如用户某个文章点击次数、观看某个视频的时长等等。

如图中操作,要依次上报 item 1 - item7,但是上滑的时候,不再重复上报

点击:加入隐藏但可滚动

再说「曝光埋点」,它与「图片懒加载」「计算广告浏览量」这些需求一样,本质就是让你计算某一元素和另一元素(视窗)的相对可视状态/相对位置,然后进行一些操作(一般是上报给服务端)。

怎么做

先来看看一个关于模态框的经典例子。当它被打开的时候,主页面应该停止滚动。在 CSS 中有如下的快捷实现方式:

思考如何实现

接到需求的第一感觉是,我难道要监听页面滚动,然后通过offsetTop 去算吗!!!??? 这样显然不是最高效的。于是我去翻了一下微信的开发文档,发现有一个IntersectionObserver 的API,正好满足我的需求。

body { overflow: hidden;}

最先出现在脑海里的方法是利用getBoundingClientRect/offset类 onscroll。即:注册滚动事件,然后在滚动的回调函数中利用getBoundingClientRect/offset类拿到每个元素的位置信息,然后经过判断确定是否元素处于曝光状态/视窗中。

首先我们需要创建一个 IntersectionObserver wx.createIntersectionObserver(Object component, Object options)

但上述代码会带来一点不良的副作用:

但这种方式有很大的缺陷。如果你熟悉浏览器的渲染过程的话,就会知道调用getBoundingClientRect/offset类会引起浏览器的回流重绘,影响网页表现/性能。频繁、大量调用更不是一个妥当的选择。

IntersectionObserver 一共有四个方法

图片 1

我开始尝试在社区找找看有没有其他更妥当的方法,还真被我找到了:Intersection Observer

IntersectionObserver.relativeTo 使用选择器指定一个节点,作为参照区域之一。 IntersectionObserver.relativeToViewport 指定页面显示区域作为参照区域之一 IntersectionObserver.observeCallback callback) 指定目标节点并开始监听相交状态变化情况 IntersectionObserver.disconnect() 停止监听。回调函数将不再触发

在这个示例中,为了演示目的,我们在 Mac 系统中设置了强制显示滚动条,因而用户体验与 Windows 用户相似。

Intersection Observer

我们这里的参照物是屏幕,所以我们使用 IntersectionObserver.relativeToViewport 来制定我们的参照物,并制定相较规则

我们该如何解决这个问题呢?如果我们知道滚动条的宽度,每次当模态框出现时,可在主页面的右边设置一点边距。

它提供了一种异步观察目标元素与祖先元素或顶级文档Viewport的交集变化的方法。也就是说,不仅可以用来获得相对于视窗的曝光,可以做得更多,这取决于“另一个元素”是什么。

Page({ data: { list: [ { value: 1, hadReport: false }, { value: 2, hadReport: false }, { value: 3, hadReport: false }, { value: 4, hadReport: false }, { value: 5, hadReport: false }, { value: 6, hadReport: false }, { value: 7, hadReport: false }, { value: 8, hadReport: false }, { value: 9, hadReport: false }, ] }, onLoad() { this._observer = this.createIntersectionObserver this._observer.relativeToViewport .observe => { const { index } = res.dataset; if (!this.data.list[index].hadReport) { console.log this.data.list[index].hadReport = true; this.setData({ list: [].concat } }) }, onUnload() { if  this._observer.disconnect

由于不同的操作系统与浏览器对滚动条的宽度不一,因而获取它的宽度并不容易。在Mac 系统中,无论任何浏览器都是统一15px,然而 Windows 系统可会令开发者发狂:

Intersection Observer将本来是开发者做的:监听滚动、遍历获取元素与另一个元素(或视窗)相对位置的工作给做了。这两块工作是页面性能损耗大户,现在交给浏览器来实现,会比我们开发者来做要妥当的多。开发者现在只需要关心其他业务逻辑即可

结论

[图片上传失败...(image-c423d3-1545732870735)]

那这么好用的API,它的兼容性状况如何呢?

类似这样的埋点,我们以后可以采用监听dom的方式去做,而不是一昧的只想着监听滚动计算位置。 除了可以做埋点上报,这种监听的方式,还是很适合去做一些图片懒加载等一系列操作。

注意,以上仅是 Windows 系统下基于当前最新版浏览器的结果。以前的版本可能有所不同,也没人知道未来会如何变化。

还不错,但兼容性方面要求高的话还是不能让人放心使用。

联想

不同于猜测,你可以通过 JavaScript 计算它的宽度(译者注:实测以下代码仅能测出原始的宽度,通过 CSS 改变了滚动条宽度后,以下代码也无法测出实际宽度):

Polyfill

上述的所有操作,都是基于微信小程序去做的,那么浏览器有没有相应的API呢?

onst outer = document.createElement;const inner = document.createElement;outer.style.overflow = 'scroll';document.body.appendChild;outer.appendChild;const scrollbarWidth = outer.offsetWidth - inner.offsetWidth;document.body.removeChild;

但不用担心,我们有polyfill。W3C提供了一个polyfill,当浏览器不支持时使用常规解决方案替代。它的思路就是在检测到当前浏览器不支持Intersection Observer API时,使用getBoundingClientRect去重新实现一遍Intersection Observer API。

浏览器是有相应的API的, Intersection Observer API ,具体的用法与上述的用法类似。

尽管仅仅七行代码(就能测出滚动条的宽度),但有数行代码是操作 DOM 的。如非必要,尽量避免进行 DOM 操作。

那么使用了该Polyfill后,浏览器兼容性状况如何呢?

 var options = { rootMargin: '0px', threshold: 1.0 } var observer = new IntersectionObserver => { console.log; observer.observe(document.querySelector;

解决这个问题的另一个方法是在模态框出现时仍保留滚动条,以下是基于这思路的纯 CSS 实现:

非常棒! (IE7都支持了,还想啥呢,大兄弟。)

注意这个会监测dom元素的可见性变化,也就是说当dom出现在视窗的时候会触发回调,消失在视窗的时候也会触发回调

tml { overflow-y: scroll;}

曝光实现步骤

但是浏览器的 IntersectionObserver 属性兼容性一般,如果想要在浏览器做曝光或者懒加载可以考虑采用原始的方法,监听浏览器滚动,并计算dom的offsetTop,可以参考笔者很久前写的 图片延时 加载原理 及应用

尽管“模态框抖动”问题解决了,但整体的外观却被一个无法使用的滚动条影响了,这无疑是设计中的硬伤。

思路就像上面一再提到的,很简单:

参考文档

在我们看来,更好的解决方案是完全地隐藏滚动条。纯粹用 CSS 也是可以实现的。该方法和 macOS 的表现并不是完全一致,滚动时滚动条仍然是不可见的。滚动条总是处于不可见状态,然而页面是可被滚动的。对于Chrome,Safari 和 Opera 而言,可以使用以下的 CSS:

new IntersectionObserver()实例化一个全局observer,(结合Vue指令)让每个DOM自行把自己加入到observer的观察列表。当某个DOM进入视窗,收集对应的信息,上报。取消对该DOM的观察。代码实现

谈谈IntersectionObserver懒加载

.container::-webkit-scrollbar { display: none;}

Exposure.ts 封装成类

微信小程序IntersectionObserver 文档

IE 或 Edge 可用以下代码:

import 'intersection-observer';export default class Exposure { private observer: IntersectionObserver | undefined; constructor() { this.init(); } private init() { const self = this; this.observer = new IntersectionObserver( (entries, observer) = { entries.forEach(item = { if (item.isIntersecting) { const data = item.target.getAttribute('data-article'); self.upload(data); observer!.unobserve(item.target); } }); }, { root: null, rootMargin: '0', threshold: 0.1, } ); } public add(el: Element) { this.observer  this.observer.observe(el); } private upload(data: string | null) { if (data) { // ajax上报数据 } }}

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

.container { -ms-overflow-style: none;}

directive/exposure.ts 封装Vue指令

至于 Firefox,很不幸,没有任何办法隐藏滚动条。

import Exposure from '@/lib/Exposure';import Vue from 'vue';const exposure = new Exposure();Vue.directive('exposure', { bind(el) { exposure.add(el); },});

正如你所见,并没有任何银弹。任何解决方案都有它的优点与缺点,应根据你项目的需要选择最合适的。

*.vue 使用指令

外观争议

div v-exposure :data-article='article'.../div

需要承认的是,滚动条的样子在部分操作系统上并不好看。一些设计师喜欢完全掌控他们应用的样式,任何一丝细节也不放过。在 GitHub 上有上百个库借助 JavaScript 取代系统滚动条的默认实现,以达到自定义的效果。

原文:

但如果你想根据现有的浏览器定制一个滚动条呢?并没有通用的 API,每个浏览器都有其独特的代码实现。

尽管5.5版本以后的 IE 浏览器允许你修改滚动条的样式,但它只允许你修改滚动条的颜色。以下是如何重新绘制拖动部分与箭头的代码:

body { scrollbar-face-color: blue;}

但只改变颜色对提高用户体验而言帮助不大。据此,WebKit 的开发者在2009年提出了样式的方案。以下是使用 -webkit 前缀在支持相关样式的浏览器中模拟 macOS 滚动条样式的代码:

::-webkit-scrollbar { width: 8px;}::-webkit-scrollbar-thumb { background-color: #c1c1c1; border-radius: 4px;}

Chrome、Safari、Opera 甚至于 UC 浏览器或者三星自带的桌面浏览器都支持。

流畅的操作体验

对于滚动而言,最常见的任务是登录页的导航。通常,它是通过锚点链接来完成的。只需要知道元素的 id 即可:

<a href="#section">Section</a>

点击该链接会 跳 到区块上, UX 设计师一般会坚持认为该过程应是平滑地运动的。GitHub 上有大量造好的轮子,然而它们或多或少都用到 JavaScript。只用一行代码也能实现同样的效果,最近DOM API 中的 Element.scrollIntoView() 可以通过传入配置对象来实现平滑滚动:

elem.scrollIntoView({ behavior: 'smooth'});

然而该属性兼容性较差且仍是通过脚本。如有可能,应尽量少用额外的脚本。

幸运的是,有一个全新的 CSS 属性,可以用简单的一行代码改变整个页面滚动的行为。

html { scroll-behavior: smooth;}

结果如下:

图片 2image

(从一个区块跳到另一个)

图片 3image

你可以在 codepen 上试验这个属性。在撰写本文时,scroll-behavior 仅在 Chrome、 Firefox 与 Opera 上被支持,但我们希望它能被广泛支持,因为使用 CSS (比使用 JavaScript)在解决页面滚动问题时优雅得多,并更符合“渐进增强”的模式。

粘性 CSS

另一个常见的需求是根据滚动方向动态地定住元素,即有名的“粘性(即 CSS 中的position: sticky)”效应。

图片 4image

在以前的日子里,要实现一个“粘性”元素需要编写复杂的滚动处理函数去计算元素的大小。该函数较难处理元素在“黏住”与“不黏住”之间微小的延迟,导致抖动的出现。通过 JavaScript 来实行也有性能上的问题,特别是在调用 [Element.getBoundingClientRect() ]时

不久之前,CSS 实现了 position: sticky 属性。只需通过指定偏移量即可实现我们想要的效果。

.element { position: sticky; top: 50px;}

剩下的就交由浏览器实现即可。你可以在 codepen 上试验一下。撰写本文之时,position: sticky 在各式浏览器上支持良好,所以如果你还在使用 JavaScript 去解决这个问题的话,是时候换成纯 CSS 的实现了。

全面使用函数节流

从浏览器的角度看来,滚动是一个事件,因此在 JavaScript 中是使用一个标准化的事件监听器 addEventListener 去处理它: ,

window.addEventListener('scroll', () => { const scrollTop = window.scrollY; /* doSomething with scrollTop */});

用户往往高频率地滚动,但如果滚动事件触发太频繁的话,会导致性能上的问题,可以通过使用函数节流这一技巧去优化它。

window.addEventListener('scroll', throttle => { const scrollTop = window.scrollY; /* doSomething with scrollTop */}));

你需要定义一个节流函数包装原来的事件监听函数,减少被包装函数的执行次数,只允许它在固定的时间间隔之内执行一次:

ction throttle(action, wait = 1000) { let time = Date.now(); return function() { if ((time   wait - Date.now < 0) { action(); time = Date.now(); } }}

为了使滚动更平滑,你可以通过使用 window.requestAnimationFrame() 来实现函数节流:

ion throttle { let isRunning = false; return function() { if (isRunning) return; isRunning = true; window.requestAnimationFrame => { action(); isRunning = false; }); }}

当然,你可以通过现有的开源轮子来实现,就像 Lodash 一样。你可以访问 codepen 来看看上述解决方案与 Lodash 中的 _.throttle 之间的区别。

使用哪个并不重要,重要的是在需要的时候,记得优化你滚动处理函数。

在视窗中显示

当你需要实现图片懒加载或者无限滚动时,需要确定元素是否出现在视窗中。这可以在事件监听器中处理,最常见的解决方案是使用 lement.getBoundingClientRect() :

window.addEventListener('scroll', () => { const rect = elem.getBoundingClientRect(); const inViewport = rect.bottom > 0 && rect.right > 0 && rect.left < window.innerWidth && rect.top < window.innerHeight;});

上述代码的问题在于每次调用 getBoundingClientRect 时都会触发回流,严重地影响了性能。在事件处理函数中调用( getBoundingClientRect )尤为糟糕,就算使用了函数节流也可能对性能没多大帮助。 (回流是指浏览器为局部或整体地重绘某个元素,需要重新计算该元素在文档中的位置与形状。)

在2016年后,可以通过使用 Intersection Observer 这一 API 来解决问题。它允许你追踪目标元素与其祖先元素或视窗的交叉状态。此外,尽管只有一部分元素出现在视窗中,哪怕只有一像素,也可以选择触发回调函数:

const observer = new IntersectionObserver(callback, options);observer.observe;

滚动边界问题

如果你的弹框或下拉列表是可滚动的,那你务必要了解连锁滚动相关的问题:当用户滚动到末尾,整个页面都会开始滚动。

图片 5image

当滚动元素到达底部时,你可以通过页面的 overflow 属性或在滚动元素的滚动事件处理函数中取消默认行为来解决这问题。

如果你选择使用 JavaScript ,请记住要处理的不是“scroll”,而是每当用户使用鼠标滚轮或触摸板时触发的“wheel”:

function handleOverscroll { const delta = -event.deltaY; if (delta < 0 && elem.offsetHeight - delta > elem.scrollHeight - elem.scrollTop) { elem.scrollTop = elem.scrollHeight; event.preventDefault(); return false; } if (delta > elem.scrollTop) { elem.scrollTop = 0; event.preventDefault(); return false; } return true;}

不幸的是,这个解决方案不太可靠。同时可能对性能产生负面影响。

过度滚动对移动端的影响尤为严重。Loren Brichter 在 iOS 的 Tweetie 应用上创造了一个“下拉刷新”的新手势,这在 UX 社区中引起了轰动:包括 Twitter 与 Facebook 在内的各大应用纷纷采用了。

当这个特性出现在安卓端的 Chrome 浏览器中时,问题出现了:它会刷新整个页面而不是加载更多的内容,成为开发者在他们的应用中实现“下拉刷新”时的麻烦。

CSS 通过 overscroll-behavior 这个新属性解决问题。它通过控制元素滚动到尽头时的行为来解决下拉刷新与连锁滚动所带来的问题,也包含针对不同平台特殊值:安卓的 glow 与 苹果系统中的 rubber band。

现在,上面 GIF 中的问题,在 Chrome、Opera 或 Firefox 中可以通过以下一行代码来解决:

.element { overscroll-behavior: contain;}

公平地说,IE 与 Edge 实现了 -ms-scroll-chaining 属性来控制连锁滚动,但它并不能处理所有的情况。幸运的是,根据这消息,微软的浏览器已经准备实现 overscroll-behavior 这一属性了。

触屏之后

触屏设备上的滚动是一个很大的话题,深入讨论需要另开一篇文章。然而,由于很多开发者忽略了这方面的内容,这里需要提及一下。

(滚动手势无处不在,令人沉迷,以至于想出了如此疯狂的主意去解决“滚动上瘾”的问题。)

周围的人在智能手机屏幕上上下移动他们的手指的频率是多少呢?经常这样对吧,当你阅读本文时,你很可能就在这么做。

当你的手指在屏幕上移动时,你期待的是:页面内容平滑且流畅地移动。

苹果公司开创了“惯性”滚动并拥有它的专利 。它讯速地成为了用户交互的标准并且我们对此已习以为常。

但你也许已经注意到了,尽管移动端系统会为你实现页面上的惯性滚动,但当页面内某个元素发生滚动时,即使用户同样期待惯性滚动,但它并不会出现,这令人沮丧。

这里有一个 CSS 的解决方案,但看起来更像是个 hack:

.element { -webkit-overflow-scrolling: touch;}

为什么这是个 hack 呢?首先,它只能在支持前缀的浏览器上才能工作。其次,它只适用于触屏设备。最后,如果浏览器不支持的话,你就这样置之不理吗?但无论如何,这总归是一个解决方案,你可以试着使用它。

在触屏设备上,另一个需要考虑的问题是开发者如何处理 touchstart 与 touchmove 事件触发时可能存在的性能问题,它对用户滚动体验的影响非常大。这里详细描述了整个问题。简单来说,现代的浏览器虽然知道如何使得滚动变得平滑,但为确认事件处理函数中是否执行了 Event.preventDefault() 以取消默认行为,有时仍可能需要花费500毫秒来等待事件处理函数执行完毕。

即使是一个空的事件监听器,从不取消任何行为,鉴于浏览器仍会期待 preventDefault 的调用,也会对性能造成负面影响。

为了准确地告诉浏览器不必担心取消了默认行为,在 WHATWG 的 DOM 标准中存在着一个不太显眼的特性。Passive event listeners,浏览器对它的支持还是不错的。事件监听函数新接受一个可选的对象作为参数,告诉浏览器当事件触发时,事件处理函数永远不会取消默认行为。(当然,添加此参数后,)在事件处理函数中调用 preventDefault 将不再产生效果。

element.addEventListener('touchstart', e => { /* doSomething */}, { passive: true });

旧技术运行良好,为何还要改动?

在现代互联网中,过渡地依赖 JavaScript 在各浏览器上实现相同的交互效果不再是合理的,“跨浏览器兼容性”已经成为过去式,更多的 CSS 属性与 DOM API 方法正逐步被各大浏览器所支持。

在我们看来,当你的项目中,有特别酷炫的滚动效果时,渐进增强是最好的做法。

你应该提供所有基础用户体验,并逐步在更先进的浏览器上提供更好的体验。

必要时使用 polyfill,它们不会产生依赖,一旦(某个 polyfill 所支持的属性)得到广泛地支持,你就可以轻松地将它删掉。

六个月之前,在本文尚未成文之时,之前我们描述的属性只被少量的浏览器所支持。而到了本文发表之时,这些属性已被广泛地支持。

也许到了现在,当你上下翻阅本文之时,(之前不支持某些属性的)浏览器已经支持了该属性,这使得你编程更容易,并使你的应用打包出来体积更小。

TAG标签:
版权声明:本文由澳门新葡8455手机版发布于Web前端,转载请注明出处:令人着迷滚动实现指南,前端埋点之曝光实现