Сколько элементов видно в окне просмотра

На странице есть div (коричневый прямоугольник). Страница выше, чем область просмотра (оранжевый прямоугольник), поэтому ее можно прокручивать, что означает, что div может отображаться только частично или не отображаться вообще.

element visibility

Какой самый простой алгоритм, чтобы сказать, сколько% div видно в окне просмотра?

(Чтобы упростить задачу, div всегда вписывается в область просмотра горизонтально, поэтому при расчетах необходимо учитывать только ось Y).

Ответ 1

См. еще один пример в скрипте: https://jsfiddle.net/1hfxom6h/3/

/*jslint browser: true*/
/*global jQuery, window, document*/
(function ($) {
    'use strict';
    var results = {};

    function display() {
        var resultString = '';

        $.each(results, function (key) {
            resultString += '(' + key + ': ' + Math.round(results[key]) + '%)';
        });

        $('p').text(resultString);
    }

    function calculateVisibilityForDiv(div$) {
        var windowHeight = $(window).height(),
            docScroll = $(document).scrollTop(),
            divPosition = div$.offset().top,
            divHeight = div$.height(),
            hiddenBefore = docScroll - divPosition,
            hiddenAfter = (divPosition + divHeight) - (docScroll + windowHeight);

        if ((docScroll > divPosition + divHeight) || (divPosition > docScroll + windowHeight)) {
            return 0;
        } else {
            var result = 100;

            if (hiddenBefore > 0) {
                result -= (hiddenBefore * 100) / divHeight;
            }

            if (hiddenAfter > 0) {
                result -= (hiddenAfter * 100) / divHeight;
            }

            return result;
        }
    }

    function calculateAndDisplayForAllDivs() {
        $('div').each(function () {
            var div$ = $(this);
            results[div$.attr('id')] = calculateVisibilityForDiv(div$);
        });

        display();
    }

    $(document).scroll(function () {
        calculateAndDisplayForAllDivs();
    });

    $(document).ready(function () {
        calculateAndDisplayForAllDivs();
    });
}(jQuery));
div {
    height:200px;
    width:300px;

    border-width:1px;
    border-style:solid;
}
p {
    position: fixed;
    left:320px;
    top:4px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="div1">div1</div>
<div id="div2">div2</div>
<div id="div3">div3</div>
<div id="div4">div4</div>
<p id="result"></p>

Ответ 2

Поиграв немного, я думаю, что я нашел, пожалуй, самый простой способ сделать это: я в основном определяю, насколько элемент распространяется на область просмотра (неважно, в каком направлении), и на основании этого это может быть легко вычислил, сколько из них видно.

// When the page is completely loaded.
$(document).ready(function() {

  // Returns in percentages how much can be seen vertically
  // of an element in the current viewport.
  $.fn.pvisible = function() {
    var eTop = this.offset().top;
    var eBottom = eTop + this.height();
    var wTop = $(window).scrollTop();
    var wBottom = wTop + $(window).height();
    var totalH = Math.max(eBottom, wBottom) - Math.min(eTop, wTop);
    var wComp = totalH - $(window).height();
    var eIn = this.height() - wComp;
    return (eIn <= 0 ? 0 : eIn / this.height() * 100);
  }

  // If the page is scrolled.
  $(window).scroll(function() {
    // Setting the opacity of the divs.
    $("div").each(function() {
      $(this).css("opacity", Math.round($(this).pvisible()) / 100);
    });
  });

});
html,
body {
  width: 100%;
  height: 100%;
}
body {
  background-color: rgba(255, 191, 127, 1);
}
div {
  width: 60%;
  height: 30%;
  margin: 5% auto;
  background-color: rgba(175, 153, 131, 1);
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>

Ответ 3

Вот фрагмент, иллюстрирующий, как вы можете это рассчитать.

Я поместил значения% в поля для удобочитаемости, и он даже вроде "следует" в окне просмотра ^^:

версия скрипта

function listVisibleBoxes() {

  var results = [];

  $("section").each(function () {

    var screenTop = document.documentElement.scrollTop;
    var screenBottom = document.documentElement.scrollTop + $(window).height();
    var boxTop = $(this).offset().top;
    var boxHeight = $(this).height();
    var boxBottom = boxTop + boxHeight;

    if(boxTop > screenTop) {
      if(boxBottom < screenBottom) {
        //full box
        results.push(this.id + "-100%");
        $(this).html("100%").css({ "line-height": "50vh" });
      } else if(boxTop < screenBottom) {
        //partial (bottom)
        var percent = Math.round((screenBottom - boxTop) / boxHeight * 100) + "%";
        var lineHeight = Math.round((screenBottom - boxTop) / boxHeight * 50) + "vh";
        results.push(this.id + "-" + percent);
        $(this).html(percent).css({ "line-height": lineHeight });
      }
    } else if(boxBottom > screenTop) {
      //partial (top)
      var percent = Math.round((boxBottom - screenTop) / boxHeight * 100) + "%";
      var lineHeight = 100 - Math.round((boxBottom - screenTop) / boxHeight * 50) + "vh";
      results.push(this.id + "-" + percent);
      $(this).html(percent).css({ "line-height": lineHeight });
    }
  });

  $("#data").html(results.join(" | "));

}

$(function () {

  listVisibleBoxes();

  $(window).on("scroll", function() {
    listVisibleBoxes();
  });

});
body {
  background-color: rgba(255, 191, 127, 1);
  font-family: Arial, sans-serif;
}

section {
  background-color: rgba(175, 153, 131, 1);
  height: 50vh;
  font-size: 5vh;
  line-height: 50vh;
  margin: 10vh auto;
  overflow: hidden;
  text-align: center;
  width: 50vw;
}

#data {
  background-color: rgba(255, 255, 255, .5);
  left: 0;
  padding: .5em;
  position: fixed;
  top: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<section id="one"></section>
<section id="two"></section>
<section id="three"></section>
<section id="four"></section>
<section id="five"></section>
<section id="six"></section>

<div id="data">data here</div>

Ответ 4

Я искал Google, и этот поток был первым в списке. Причина, из-за которой он снова превзошел, - это то, что другие, подобные мне в поиске того, как сделать эту проверку, имеют быструю информацию о более современном решении: IntersectionObserver.

https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Ответ 5

Chrome теперь поддерживает Intersection Observer API:
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API

Пример (TypeScript):

export const elementVisibleInPercent = (element: HTMLElement) => {
return new Promise((resolve, reject) => {
    const observer = new IntersectionObserver((entries: IntersectionObserverEntry[]) => {
        entries.forEach((entry: IntersectionObserverEntry) => {
            resolve(Math.floor(entry.intersectionRatio * 100));
            clearTimeout(timeout);
            observer.disconnect();
        });
    });

    observer.observe(element);
    // Probably not needed, but in case something goes wrong.
    const timeout = setTimeout(() => {
        reject();
    }, 500);
});

};

Пример (JavaScript):

export const elementVisibleInPercent = (element) => {
return new Promise((resolve, reject) => {
    const observer = new IntersectionObserver(entries => {
        entries.forEach(entry => {
            resolve(Math.floor(entry.intersectionRatio * 100));
            clearTimeout(timeout);
            observer.disconnect();
        });
    });

    observer.observe(element);
    // Probably not needed, but in case something goes wrong.
    const timeout = setTimeout(() => {
        reject();
    }, 500);
});

};