整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

滚动到可视窗口区域

滚动到可视窗口区域
// 定制详情页之选择栏目
$(document).off('click','.js-nav-headerwrap>a').on('click','.js-nav-headerwrap>a',function(){
    var _$this=$(this);
    _$this.addClass('active').siblings().removeClass('active');
    $('.cs-custommade').removeClass('isactive');
    if (_$this.attr('data-set')) {
        $('.'+ _$this.attr('data-set')).addClass('isactive');
    } 
    //如果固定
    if ($('.js-nav-headerwrap').hasClass('fixed')) {
        var scrollTop=$(window).scrollTop();
        var numTop=$('.apply-main').height() + $('.courser-info-second').height()+  $('.course-studytime-new22').height();
        console.log(numTop);
      // 距离
        $("body,html").animate({
            scrollTop: numTop 
        }, 300);
    }
    
});


可视区域即我们浏览网页的设备肉眼可见的区域,如下图

在日常开发中,我们经常需要判断目标元素是否在视窗之内或者和视窗的距离小于一个值(例如 100 px),从而实现一些常用的功能,例如:

  • 图片的懒加载
  • 列表的无限滚动
  • 计算广告元素的曝光情况
  • 可点击链接的预加载

实现方式

判断一个元素是否在可视区域,我们常用的有三种办法:

  • offsetTop、scrollTop
  • getBoundingClientRect
  • Intersection Observer

offsetTop、scrollTop

offsetTop,元素的上外边框至包含元素的上内边框之间的像素距离,其他offset属性如下图所示:

下面再来了解下clientWidthclientHeight

  • clientWidth:元素内容区宽度加上左右内边距宽度,即clientWidth=content + padding
  • clientHeight:元素内容区高度加上上下内边距高度,即clientHeight=content + padding

这里可以看到client元素都不包括外边距

最后,关于scroll系列的属性如下:

  • scrollWidthscrollHeight 主要用于确定元素内容的实际大小
  • scrollLeftscrollTop 属性既可以确定元素当前滚动的状态,也可以设置元素的滚动位置
  • 垂直滚动 scrollTop > 0水平滚动 scrollLeft > 0
  • 将元素的 scrollLeftscrollTop 设置为 0,可以重置元素的滚动位置

注意

  • 上述属性都是只读的,每次访问都要重新开始

下面再看看如何实现判断:

公式如下:

el.offsetTop - document.documentElement.scrollTop <=viewPortHeight

代码实现:

function isInViewPortOfOne (el) {
    // viewPortHeight 兼容所有浏览器写法
    const viewPortHeight=window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight 
    const offsetTop=el.offsetTop
    const scrollTop=document.documentElement.scrollTop
    const top=offsetTop - scrollTop
    return top <=viewPortHeight
}

getBoundingClientRect

rectObject.top:元素上边到视窗上边的距离;
rectObject.right:元素右边到视窗左边的距离;
rectObject.bottom:元素下边到视窗上边的距离;
rectObject.left:元素左边到视窗左边的距离;
rectObject.width:是元素自身的宽度
rectObject.height是元素自身的高度

getBoundingClientRect()获取元素位置,这个方法没有参数

getBoundingClientRect用于获得页面中某个元素的左,上,右和下分别相对浏览器视窗的位置。
getBoundingClientRect是DOM元素到浏览器可视范围的距离(不包含文档卷起的部分)。
该函数返回一个Object对象,该对象有6个属性:top,lef,right,bottom,width,height;

  • width、height是元素自身的宽高
  • right是指元素右边界距窗口最左边的距离,bottom是指元素下边界距窗口最上面的距离
document.body.getBoundingClientRect().width

返回值是一个 DOMRect对象,拥有left, top, right, bottom, x, y, width, 和 height属性

const target=document.querySelector('.target');
const clientRect=target.getBoundingClientRect();
console.log(clientRect);

// {
//   bottom: 556.21875,
//   height: 393.59375,
//   left: 333,
//   right: 1017,
//   top: 162.625,
//   width: 684
// }

属性对应的关系图如下所示:

当页面发生滚动的时候,topleft属性值都会随之改变

如果一个元素在视窗之内的话,那么它一定满足下面四个条件:

  • top 大于等于 0
  • left 大于等于 0
  • bottom 小于等于视窗高度
  • right 小于等于视窗宽度

实现代码如下:

function isInViewPort(element) {
  const viewWidth=window.innerWidth || document.documentElement.clientWidth;
  const viewHeight=window.innerHeight || document.documentElement.clientHeight;
  const {
    top,
    right,
    bottom,
    left,
  }=element.getBoundingClientRect();

  return (
    top >=0 &&
    left >=0 &&
    right <=viewWidth &&
    bottom <=viewHeight
  );
}

Intersection Observer

Interp ObserverAPI是什么

我们需要观察的元素被称为 目标元素(target),设备视窗或者其他指定的元素视口的边界框我们称它为 根元素(root),或者简称为

Interp Observer API 翻译过来叫做 “交叉观察器”,因为判断元素是否可见(通常情况下)的本质就是判断目标元素和根元素是不是产生了 交叉区域

为什么是通常情况下,因为当我们 css 设置了 opacity: 0,visibility: hidden 或者 用其他的元素覆盖目标元素 的时候,对于视图来说是不可见的,但对于交叉观察器来说是可见的。这里可能有点抽象,大家只需记住,交叉观察器只关心 目标元素根元素 是否有 交叉区域, 而不管视觉上能不能看见这个元素。当然如果设置了 display:none,那么交叉观察器就不会生效了,其实也很好理解,因为元素已经不存在了,那么也就监测不到了。

一句话总结:Interp Observer API 提供了一种异步检测目标元素与祖先元素或 viewport 相交情况变化的方法。 -- MDN

现在不懂没关系,咱们接着往下看,看完自然就明白了。

Interp Observer API 怎么玩

生成观察器

// 调用构造函数 InterpObserver 生成观察器
const myObserver=new InterpObserver(callback, options);  

首先调用浏览器原生构造函数 InterpObserver ,构造函数的返回值是一个 观察器实例

构造函数 InterpObserver 接收两个参数

  • callback: 可见性发生变化时触发的回调函数
  • options: 配置对象(可选,不传时会使用默认配置)

使用步骤主要分为两步:创建观察者和传入被观察者

构造函数接收的参数 options

为了方便理解,我们先看第二个参数 options 。一个可以用来配置观察器实例的对象,那么这个配置对象都包含哪些属性呢?

  • root: 设置目标元素的根元素,也就是我们用来判断元素是否可见的区域,必须是目标元素的父级元素,如果不指定的话,则使用浏览器视窗,也就是 document。
  • rootMargin: 一个在计算交叉值时添加至根的边界中的一组偏移量,类型为字符串 (string) ,可以有效的缩小或扩大根的判定范围从而满足计算需要。语法大致和CSS 中 margin 属性等同,默认值 “0px 0px 0px 0px” ,如果有指定 root 参数,则 rootMargin 也可以使用百分比来取值。
  • threshold: 介于 0 和 1 之间的数字,指示触发前应可见的百分比。也可以是一个数字数组,以创建多个触发点,也被称之为 阈值。如果构造器未传入值, 则默认值为 0 。
  • trackVisibility: 一个布尔值,指示当前观察器是否将跟踪目标可见性的更改,默认为 false ,注意,此处的可见性并非指目标元素和根元素是否相交,而是指视图上是否可见,这个我们之前就已经分析过了,如果此值设置为 false 或不设置,那么回调函数参数中 InterpObserverEntry 的 isVisible 属性将永远返回 false 。
  • delay: 一个数字,也就是回调函数执行的延迟时间(毫秒)。如果 trackVisibility 设置为 true,则此值必须至少设置为 100 ,否则会报错(但是这里我也只是亲测出来的,并不知道为什么会设计成这样,如果有大佬了解请指教一下)。

创建观察者

const options={
  // 表示重叠面积占被观察者的比例,从 0 - 1 取值,
  // 1 表示完全被包含
  threshold: 1.0, 
  root:document.querySelector('#scrollArea') // 必须是目标元素的父级元素
};

const callback=(entries, observer)=> { ....}

const observer=new IntersectionObserver(callback, options);

通过new IntersectionObserver创建了观察者 observer,传入的参数 callback 在重叠比例超过 threshold 时会被执行`

关于callback回调函数常用属性如下:

// 上段代码中被省略的 callback
const callback=function(entries, observer) { 
    entries.forEach(entry=> {
        entry.time;               // 触发的时间
        entry.rootBounds;         // 根元素的位置矩形,这种情况下为视窗位置
        entry.boundingClientRect; // 被观察者的位置举行
        entry.intersectionRect;   // 重叠区域的位置矩形
        entry.intersectionRatio;  // 重叠区域占被观察者面积的比例(被观察者不是矩形时也按照矩形计算)
        entry.target;             // 被观察者
    });
};

传入被观察者

通过 observer.observe(target) 这一行代码即可简单的注册被观察者

const target=document.querySelector('.target');
observer.observe(target);

案例分析

实现:创建了一个十万个节点的长列表,当节点滚入到视窗中时,背景就会从红色变为黄色

Html结构如下:

<div class="container"></div>

css样式如下:

.container {
    display: flex;
    flex-wrap: wrap;
}
.target {
    margin: 5px;
    width: 20px;
    height: 20px;
    background: red;
}

container插入1000个元素

const $container=$(".container");

// 插入 100000 个 <div class="target"></div>
function createTargets() {
  const htmlString=new Array(100000)
    .fill('<div class="target"></div>')
    .join("");
  $container.html(htmlString);
}

这里,首先使用getBoundingClientRect方法进行判断元素是否在可视区域

function isInViewPort(element) {
    const viewWidth=window.innerWidth || document.documentElement.clientWidth;
    const viewHeight=  window.innerHeight || document.documentElement.clientHeight;
    const { top, right, bottom, left }=element.getBoundingClientRect();

    return top >=0 && left >=0 && right <=viewWidth && bottom <=viewHeight;
}

然后开始监听scroll事件,判断页面上哪些元素在可视区域中,如果在可视区域中则将背景颜色设置为yellow

$(window).on("scroll", ()=> {
    console.log("scroll !");
    $targets.each((index, element)=> {
        if (isInViewPort(element)) {
            $(element).css("background-color", "yellow");
        }
    });
});

通过上述方式,可以看到可视区域颜色会变成黄色了,但是可以明显看到有卡顿的现象,原因在于我们绑定了scroll事件,scroll事件伴随了大量的计算,会造成资源方面的浪费

下面通过Intersection Observer的形式同样实现相同的功能

首先创建一个观察者

const observer=new IntersectionObserver(getYellow, { threshold: 1.0 });

getYellow回调函数实现对背景颜色改变,如下:

function getYellow(entries, observer) {
    entries.forEach(entry=> {
        $(entry.target).css("background-color", "yellow");
    });
}

最后传入观察者,即.target元素

$targets.each((index, element)=> {
    observer.observe(element);
});

可以看到功能同样完成,并且页面不会出现卡顿的情况

给大家分享我收集整理的各种学习资料,前端小白交学习流程,入门教程等回答-下面是学习资料参考。

前端学习交流、自学、学习资料等推荐 - 知乎


加载


什么是懒加载

懒加载其实就是延迟加载,是一种对网页性能优化可方式,比如当访问一个页面的时候,优先显示可视区域的图片而不一次性加载所有图片,当需要显示的时候再发送图片请求,避免打开网页时加载过多资源。

什么时候用懒加载

当页面中需要一次性载入很多图片的时候,往往都是需要用懒加载的。

懒加载原理

我们都知道HTML中的 <img>标签是代表文档中的一个图像。。说了个废话。。

<img>标签有一个属性是 src,用来表示图像的URL,当这个属性的值不为空时,浏览器就会根据这个值发送请求。如果没有 src属性,就不会发送请求。

嗯?貌似这点可以利用一下?

我先不设置 src,需要的时候再设置?

nice,就是这样。

我们先不给 <img>设置 src,把图片真正的URL放在另一个属性 data-src中,在需要的时候也就是图片进入可视区域的之前,将URL取出放到 src中。

实现


HTML结构

<div class="container">
 <div class="img-area">
   <img class="my-photo" alt="loading" data-src="./img/img1.png">
 </div>
 <div class="img-area">
   <img class="my-photo" alt="loading" data-src="./img/img2.png">
 </div>
 <div class="img-area">
   <img class="my-photo" alt="loading" data-src="./img/img3.png">
 </div>
 <div class="img-area">
   <img class="my-photo" alt="loading" data-src="./img/img4.png">
 </div>
 <div class="img-area">
   <img class="my-photo" alt="loading" data-src="./img/img5.png">
 </div>
</div>

仔细观察一下, <img>标签此时是没有 src属性的,只有 alt和 data-src属性。

alt 属性是一个必需的属性,它规定在图像无法显示时的替代文本。 data-* 全局属性:构成一类名称为自定义数据属性的属性,可以通过 HTMLElement.dataset来访问。

如何判断元素是否在可视区域

方法一

网上看到好多这种方法,稍微记录一下。

  1. 通过 document.documentElement.clientHeight获取屏幕可视窗口高度
  2. 通过 document.documentElement.scrollTop获取浏览器窗口顶部与文档顶部之间的距离,也就是滚动条滚动的距离
  3. 通过 element.offsetTop获取元素相对于文档顶部的距离

然后判断②-③<①是否成立,如果成立,元素就在可视区域内。

方法二(推荐)

通过 getBoundingClientRect()方法来获取元素的大小以及位置,MDN上是这样描述的:

The Element.getBoundingClientRect() method returns the size of an element and its position relative to the viewport.

这个方法返回一个名为 ClientRect的 DOMRect对象,包含了 top、 right、 botton、 left、 width、 height这些值。

MDN上有这样一张图:

可以看出返回的元素位置是相对于左上角而言的,而不是边距。

我们思考一下,什么情况下图片进入可视区域。

假设 constbound=el.getBoundingClientRect();来表示图片到可视区域顶部距离; 并设 constclientHeight=window.innerHeight;来表示可视区域的高度。

随着滚动条的向下滚动, bound.top会越来越小,也就是图片到可视区域顶部的距离越来越小,当 bound.top===clientHeight时,图片的上沿应该是位于可视区域下沿的位置的临界点,再滚动一点点,图片就会进入可视区域。

也就是说,在 bound.top<=clientHeight时,图片是在可视区域内的。

我们这样判断:

function isInSight(el) {
 const bound=el.getBoundingClientRect();
 const clientHeight=window.innerHeight;
 //如果只考虑向下滚动加载
 //const clientWidth=window.innerWeight;
 return bound.top <=clientHeight + 100;
}

这里有个+100是为了提前加载。

加载图片

页面打开时需要对所有图片进行检查,是否在可视区域内,如果是就加载。

function checkImgs() {
 const imgs=document.querySelectorAll('.my-photo');
 Array.from(imgs).forEach(el=> {
   if (isInSight(el)) {
     loadImg(el);
   }
 })
}

function loadImg(el) {
 if (!el.src) {
   const source=el.dataset.src;
   el.src=source;
 }
}

这里应该是有一个优化的地方,设一个标识符标识已经加载图片的index,当滚动条滚动时就不需要遍历所有的图片,只需要遍历未加载的图片即可。

函数节流

在类似于滚动条滚动等频繁的DOM操作时,总会提到“函数节流、函数去抖”。

所谓的函数节流,也就是让一个函数不要执行的太频繁,减少一些过快的调用来节流。

基本步骤:

  1. 获取第一次触发事件的时间戳
  2. 获取第二次触发事件的时间戳
  3. 时间差如果大于某个阈值就执行事件,然后重置第一个时间
function throttle(fn, mustRun=500) {
 const timer=null;
 let previous=null;
 return function() {
   const now=new Date();
   const context=this;
   const args=arguments;
   if (!previous){
     previous=now;
   }
   const remaining=now - previous;
   if (mustRun && remaining >=mustRun) {
     fn.apply(context, args);
     previous=now;
   }
 }
}

这里的 mustRun就是调用函数的时间间隔,无论多么频繁的调用 fn,只有 remaining>=mustRun时 fn才能被执行。

实验


页面打开时

可以看出此时仅仅是加载了img1和img2,其它的img都没发送请求,看看此时的浏览器

第一张图片是完整的呈现了,第二张图片刚进入可视区域,后面的就看不到了~

页面滚动时

当我向下滚动,此时浏览器是这样

此时第二张图片完全显示了,而第三张图片显示了一点点,这时候我们看看请求情况

img3的请求发出来,而后面的请求还是没发出~

全部载入时

当滚动条滚到最底下时,全部请求都应该是发出的,如图

更新


方法三 IntersectionObserver

经大佬提醒,发现了这个方法

先附上链接:

jjc大大:

https://github.com/justjavac/the-front-end-knowledge-you-may-dont-know/issues/10

阮一峰大大:

http://www.ruanyifeng.com/blog/2016/11/intersectionobserver_api.html

API Sketch for Intersection Observers:

https://github.com/WICG/IntersectionObserver

IntersectionObserver可以自动观察元素是否在视口内。

var io=new IntersectionObserver(callback, option);
// 开始观察
io.observe(document.getElementById('example'));
// 停止观察
io.unobserve(element);
// 关闭观察器
io.disconnect();

callback的参数是一个数组,每个数组都是一个 IntersectionObserverEntry对象,包括以下属性:

属性描述time可见性发生变化的时间,单位为毫秒rootBounds与getBoundingClientRect()方法的返回值一样boundingClientRect目标元素的矩形区域的信息intersectionRect目标元素与视口(或根元素)的交叉区域的信息intersectionRatio目标元素的可见比例,即intersectionRect占boundingClientRect的比例,完全可见时为1,完全不可见时小于等于0target被观察的目标元素,是一个 DOM 节点对象

我们需要用到 intersectionRatio来判断是否在可视区域内,当 intersectionRatio>0&&intersectionRatio<=1即在可视区域内。

代码

function checkImgs() {
 const imgs=Array.from(document.querySelectorAll(".my-photo"));
 imgs.forEach(item=> io.observe(item));
}

function loadImg(el) {
 if (!el.src) {
   const source=el.dataset.src;
   el.src=source;
 }
}

const io=new IntersectionObserver(ioes=> {
 ioes.forEach(ioe=> {
   const el=ioe.target;
   const intersectionRatio=ioe.intersectionRatio;
   if (intersectionRatio > 0 && intersectionRatio <=1) {
     loadImg(el);
   }
   el.onload=el.onerror=()=> io.unobserve(el);
 });
});

源自:segmentfault

声明:文章著作权归作者所有,如有侵权,请联系小编删除。