使用“路口观察器"(IO)更改样式标题/导航 [英] Change style header/nav with Intersection Observer (IO)

查看:75
本文介绍了使用“路口观察器"(IO)更改样式标题/导航的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

提要最新


我从scroll event方法开始了这个问题,但是由于使用IntersectionObserver的建议,这似乎是更好的方法,因此我试图使其以这种方式工作.


目标是什么

我想通过查找(我在想什么?)其classdata将会覆盖默认的header样式(白色上的黑色).


标题样式:

font-color:

根据内容(div/section),默认的header应该能够将font-color更改为仅两种可能的颜色:

  • 黑色
  • 白色

background-color:

根据内容,background-color可以具有无限的颜色或透明的,因此最好将它们分开处理,这些可能是最常用的背景颜色:

  • 白色(默认)
  • 黑色
  • 无颜色(透明)

CSS:

header {
  position: fixed;
  width: 100%;
  top: 0;
  line-height: 32px;
  padding: 0 15px;
  z-index: 5;
  color: black; /* default */
  background-color: white; /* default */
}

具有默认标题的细分/节示例,内容无变化:

<div class="grid-30-span g-100vh">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_default_header.jpg" 
    class="lazyload"
    alt="">
</div>

分区/节"示例更改内容标题:

<div class="grid-30-span g-100vh" data-color="white" data-background="darkblue">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_darkblue.jpg" 
    class="lazyload"
    alt="">
</div>

<div class="grid-30-span g-100vh" data-color="white" data-background="black">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_black.jpg" 
    class="lazyload"
    alt="">
</div>

路口观察员方法:

var mq = window.matchMedia( "(min-width: 568px)" );
if (mq.matches) {
  // Add for mobile reset

document.addEventListener("DOMContentLoaded", function(event) { 
  // Add document load callback for leaving script in head
  const header = document.querySelector('header');
  const sections = document.querySelectorAll('div');
  const config = {
    rootMargin: '0px',
    threshold: [0.00, 0.95]
  };

  const observer = new IntersectionObserver(function (entries, self) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        if (entry.intersectionRatio > 0.95) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";   
        } else {
        if (entry.target.getBoundingClientRect().top < 0 ) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
          }
        } 
      }
    });
  }, config);

  sections.forEach(section => {
    observer.observe(section);
  });

});

}

解决方案

与其监听滚动事件,不如看看这是一个Codepen ,它显示了您的问题的解决方案. 我不是该Codepen的作者,我可能会做一些不同的事情,但是它肯定向您显示了解决问题的基本方法.

我将要更改的内容:在示例中,您可以看到,如果将99%的内容添加到新部分中,则标题变化甚至变得很困难,新部分也不是完全可见的.

现在解决了这个问题,对它的工作原理进行了一些解释(注意,我不会盲目地从codepen复制粘贴,我还将const更改为let,而是使用更适合您项目的东西.

首先,您必须指定IO选项:

let options = {
  rootMargin: '-50px 0px -55%'
}

let observer = new IntersectionObserver(callback, options);

在此示例中,一旦元素距离视图50px,则IO将执行回调.我不能一味推荐一些更好的值,但如果有时间,我会尝试调整这些参数以查看是否可以获得更好的结果.

在codepen中,它们定义了内联的回调函数,我只是用这种方式编写了它,以使之更清楚地了解发生在哪里的情况.

IO的下一步是定义一些要监视的元素.在您的情况下,您应该向div中添加一些类,例如<div class="section">

let entries = document.querySelectorAll('div.section');
entries.forEach(entry => {observer.observe(entry);})

最后,您必须定义回调函数:

entries.forEach(entry => {
    if (entry.isIntersecting) {
     //specify what should happen if an element is coming into view, like defined in the options. 
    }
  });

正如我所说的,这只是一个入门的示例,不是复制粘贴的完整解决方案.在基于可见部分的示例中,当前元素被突出显示.您必须更改此部分,以便不要将活动类设置为例如第三个元素,而是根据在Element上设置的某些属性来设置颜色和背景色.我建议为此使用数据属性.

当然,您可以继续使用滚动事件,官方Polyfill来自W3C的使用滚动事件来模拟旧版浏览器的IO.仅仅是侦听滚动事件和计算位置不起作用,特别是在有多个元素的情况下.因此,如果您关心用户体验,我真的建议您使用IO.只是想添加此答案,以显示解决此问题的现代解决方案.

我花了一些时间来创建基于IO的示例,这应该可以帮助您入门.

我基本上定义了两个阈值:一个为20%,一个为90%.如果该元素在视口中为90%,则保存它会覆盖标题.因此,我将标头的类设置为视图中90%的元素.

第二个阈值为20%,在这里我们必须检查元素是从顶部还是从底部进入视图.如果从顶部可见20%,则它将与标题重叠.

调整这些值并调整逻辑,如您所见.

 const sections = document.querySelectorAll('div');
const config = {
  rootMargin: '0px',
  threshold: [.2, .9]
};

const observer = new IntersectionObserver(function (entries, self) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      var headerEl = document.querySelector('header');
      if (entry.intersectionRatio > 0.9) {
        //intersection ratio bigger than 90%
        //-> set header according to target
        headerEl.className=entry.target.dataset.header;      
      } else {
        //-> check if element is coming from top or from bottom into view
        if (entry.target.getBoundingClientRect().top < 0 ) {
          headerEl.className=entry.target.dataset.header;
        }
      } 
    }
  });
}, config);

sections.forEach(section => {
  observer.observe(section);
}); 

 * {
margin: 0;
padding: 0;
box-sizing: border-box;
}

.g-100vh {
height: 100vh
}

header {
  min-height: 50px;
  position: fixed;
  background-color: green;
  width: 100%;
}
  
header.white-menu {
  color: white;
  background-color: black;
}

header.black-menu {
  color: black;
  background-color: white;
} 

 <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<header>
 <p>Header Content </p>
</header>
<div class="grid-30-span g-100vh white-menu" style="background-color:darkblue;" data-header="white-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_darkblue.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div>

<div class="grid-30-span g-100vh black-menu" style="background-color:lightgrey;" data-header="black-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_lightgrey.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div> 

Fiddle latest


I started this question with the scroll event approach, but due to the suggestion of using IntersectionObserver which seems much better approach i'm trying to get it to work in that way.


What is the goal:

I would like to change the style (color+background-color) of the header depending on what current div/section is observed by looking for (i'm thinking of?) its class or data that will override the default header style (black on white).


Header styling:

font-color:

Depending on the content (div/section) the default header should be able to change the font-color into only two possible colors:

  • black
  • white

background-color:

Depending on the content the background-color could have unlimited colors or be transparent, so would be better to address that separate, these are the probably the most used background-colors:

  • white (default)
  • black
  • no color (transparent)

CSS:

header {
  position: fixed;
  width: 100%;
  top: 0;
  line-height: 32px;
  padding: 0 15px;
  z-index: 5;
  color: black; /* default */
  background-color: white; /* default */
}

Div/section example with default header no change on content:

<div class="grid-30-span g-100vh">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_default_header.jpg" 
    class="lazyload"
    alt="">
</div>

Div/section example change header on content:

<div class="grid-30-span g-100vh" data-color="white" data-background="darkblue">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_darkblue.jpg" 
    class="lazyload"
    alt="">
</div>

<div class="grid-30-span g-100vh" data-color="white" data-background="black">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_black.jpg" 
    class="lazyload"
    alt="">
</div>

Intersection Observer approach:

var mq = window.matchMedia( "(min-width: 568px)" );
if (mq.matches) {
  // Add for mobile reset

document.addEventListener("DOMContentLoaded", function(event) { 
  // Add document load callback for leaving script in head
  const header = document.querySelector('header');
  const sections = document.querySelectorAll('div');
  const config = {
    rootMargin: '0px',
    threshold: [0.00, 0.95]
  };

  const observer = new IntersectionObserver(function (entries, self) {
    entries.forEach(entry => {
      if (entry.isIntersecting) {
        if (entry.intersectionRatio > 0.95) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";   
        } else {
        if (entry.target.getBoundingClientRect().top < 0 ) {
          header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
          header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
          }
        } 
      }
    });
  }, config);

  sections.forEach(section => {
    observer.observe(section);
  });

});

}

解决方案

Instead of listening to scroll event you should have a look at Intersection Observer (IO). This was designed to solve problems like yours. And it is much more performant than listening to scroll events and then calculating the position yourself.

First, here is a codepen which shows a solution for your problem. I am not the author of this codepen and I would maybe do some things a bit different but it definitely shows you the basic approach on how to solve your problem.

Things I would change: You can see in the example that if you scoll 99% to a new section, the heading changes even tough the new section is not fully visible.

Now with that out of the way, some explaining on how this works (note, I will not blindly copy-paste from codepen, I will also change const to let, but use whatever is more appropriate for your project.

First, you have to specify the options for IO:

let options = {
  rootMargin: '-50px 0px -55%'
}

let observer = new IntersectionObserver(callback, options);

In the example the IO is executing the callback once an element is 50px away from getting into view. I can't recommend some better values from the top of my head but if I would have the time I would try to tweak these parameters to see if I could get better results.

In the codepen they define the callback function inline, I just wrote it that way to make it clearer on what's happening where.

Next step for IO is to define some elements to watch. In your case you should add some class to your divs, like <div class="section">

let entries = document.querySelectorAll('div.section');
entries.forEach(entry => {observer.observe(entry);})

Finally you have to define the callback function:

entries.forEach(entry => {
    if (entry.isIntersecting) {
     //specify what should happen if an element is coming into view, like defined in the options. 
    }
  });

Edit: As I said this is just an example on how to get you started, it's NOT a finished solution for you to copy paste. In the example based on the ID of the section that get's visible the current element is getting highlighted. You have to change this part so that instead of setting the active class to, for example, third element you set the color and background-color depending on some attribute you set on the Element. I would recommend using data attributes for that.

Edit 2: Of course you can continue using just scroll events, the official Polyfill from W3C uses scroll events to emulate IO for older browsers.it's just that listening for scroll event and calculating position is not performant, especially if there are multiple elements. So if you care about user experience I really recommend using IO. Just wanted to add this answer to show what the modern solution for such a problem would be.

Edit 3: I took my time to create an example based on IO, this should get you started.

Basically I defined two thresholds: One for 20 and one for 90%. If the element is 90% in the viewport then it's save to assume it will cover the header. So I set the class for the header to the element that is 90% in view.

Second threshold is for 20%, here we have to check if the element comes from the top or from the bottom into view. If it's visible 20% from the top then it will overlap with the header.

Adjust these values and adapt the logic as you see.

const sections = document.querySelectorAll('div');
const config = {
  rootMargin: '0px',
  threshold: [.2, .9]
};

const observer = new IntersectionObserver(function (entries, self) {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      var headerEl = document.querySelector('header');
      if (entry.intersectionRatio > 0.9) {
        //intersection ratio bigger than 90%
        //-> set header according to target
        headerEl.className=entry.target.dataset.header;      
      } else {
        //-> check if element is coming from top or from bottom into view
        if (entry.target.getBoundingClientRect().top < 0 ) {
          headerEl.className=entry.target.dataset.header;
        }
      } 
    }
  });
}, config);

sections.forEach(section => {
  observer.observe(section);
});

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}

.g-100vh {
height: 100vh
}

header {
  min-height: 50px;
  position: fixed;
  background-color: green;
  width: 100%;
}
  
header.white-menu {
  color: white;
  background-color: black;
}

header.black-menu {
  color: black;
  background-color: white;
}

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>


<header>
 <p>Header Content </p>
</header>
<div class="grid-30-span g-100vh white-menu" style="background-color:darkblue;" data-header="white-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_darkblue.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div>

<div class="grid-30-span g-100vh black-menu" style="background-color:lightgrey;" data-header="black-menu">
    <img 
    src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
    data-src="/images/example_lightgrey.jpg" 
    class="lazyload"
    alt="<?php echo $title; ?>">
</div>

这篇关于使用“路口观察器"(IO)更改样式标题/导航的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆