Подтвердить что ты не робот

Эффективно получить координаты видимых элементов элемента

StackOverflow загружен с questions о том, как проверить, действительно ли элемент виден в окне просмотра, но все они ищут логический ответ. Я заинтересован в получении видимых фактических областей элемента.

function getVisibleAreas(e) {
    ...
    return rectangleSet;
}

Положив его более формально - видимые области элементов - это набор (предпочтительно неперекрывающихся) прямоугольников в координатах CSS, для которых elementFromPoint(x, y) вернет элемент, если точка (x, y) содержится в (по крайней мере) одном из прямоугольников в наборе.

Результатом вызова этой функции на всех элементах DOM (включая iframes) должен быть набор неперекрывающихся наборов областей, объединение которых является всей областью просмотра.

Моя цель - создать какую-то структуру данных "дампа" в виде просмотра, которая может эффективно возвращать один элемент для данной точки в окне просмотра, и наоборот - для данного элемента в дампе он вернет набор видимых областей. (Структура данных будет передана удаленному клиентскому приложению, поэтому я не обязательно буду иметь доступ к фактическому документу, когда мне нужно запросить структуру видового экрана).

Требования к внедрению:

Очевидно, что реализация должна учитывать состояние элемента hidden, z-index, верхний и нижний колонтитулы и т.д. Я ищу реализацию, которая работает во всех распространенных браузерах, особенно мобильных - Android Chrome и iOS Safari. Предпочтительно не использовать внешние библиотеки.

Конечно, я мог бы быть наивным и называть elementFromPoint для каждой дискретной точки в окне просмотра. Но производительность имеет решающее значение, поскольку я повторяю все элементы и буду делать это довольно часто.

Пожалуйста, направляйте меня, как я могу достичь этой цели.

Отказ от ответственности: Я довольно хорошо знаком с концепциями веб-программирования, поэтому я мог использовать неправильные технические термины.

Прогресс:

Я придумал реализацию. Алгоритм довольно прост:

  • Итерации по всем элементам и добавление их вертикальных/горизонтальных линий к карте координат (если координата находится в окне просмотра).
  • Вызовите `document.elementFromPoint` для каждой центральной позиции "прямоугольник". Прямоугольник представляет собой область между двумя последовательными вертикальными и двумя последовательными горизонтальными координатами на карте с шага 1.

Это создает набор областей/прямоугольников, каждый из которых указывает на один элемент.

Проблемы с моей реализацией:

  • Это неэффективно для сложных страниц (может занимать до 2-4 минут для действительно большого экрана и входящих сообщений gmail).
  • Он создает большое количество прямоугольников для одного элемента, что делает его неэффективным для строения и отправки по сети, а также неудобно работать (я бы хотел, чтобы в итоге получилось множество с как можно меньшим количеством прямоугольников в элемент).

Насколько я могу судить, вызов elementFromPoint - это тот, который занимает много времени и заставляет мой алгоритм быть относительно бесполезным...

Может ли кто-нибудь предложить лучший подход?

Вот моя реализация:

function AreaPortion(l, t, r, b, currentDoc) {
    if (!currentDoc) currentDoc = document;
    this._x = l;
    this._y = t;
    this._r = r;
    this._b = b;
    this._w = r - l;
    this._h = b - t;

    center = this.getCenter();
    this._elem = currentDoc.elementFromPoint(center[0], center[1]);
}

AreaPortion.prototype = {
    getName: function() {
        return "[x:" + this._x + ",y:" + this._y + ",w:" + this._w + ",h:" + this._h + "]";
    },

    getCenter: function() {
        return [this._x + (this._w / 2), this._y + (this._h / 2)];
    }
}

function getViewport() {
    var viewPortWidth;
    var viewPortHeight;

    // IE6 in standards compliant mode (i.e. with a valid doctype as the first line in the document)
    if (
            typeof document.documentElement != 'undefined' &&
            typeof document.documentElement.clientWidth != 'undefined' &&
            document.documentElement.clientWidth != 0) {
        viewPortWidth = document.documentElement.clientWidth,
        viewPortHeight = document.documentElement.clientHeight
    }

    // the more standards compliant browsers (mozilla/netscape/opera/IE7) use window.innerWidth and window.innerHeight
    else if (typeof window.innerWidth != 'undefined') {
        viewPortWidth = window.innerWidth,
        viewPortHeight = window.innerHeight
    }

    // older versions of IE
    else {
        viewPortWidth = document.getElementsByTagName('body')[0].clientWidth,
        viewPortHeight = document.getElementsByTagName('body')[0].clientHeight
    }

    return [viewPortWidth, viewPortHeight];
}

function getLines() {
    var onScreen = [];
    var viewPort = getViewport();
    // TODO: header & footer
    var all = document.getElementsByTagName("*");

    var vert = {};
    var horz = {};

    vert["0"] = 0;
    vert["" + viewPort[1]] = viewPort[1];
    horz["0"] = 0;
    horz["" + viewPort[0]] = viewPort[0];
    for (i = 0 ; i < all.length ; i++) {
        var e = all[i];
        // TODO: Get all client rectangles
        var rect = e.getBoundingClientRect();
        if (rect.width < 1 && rect.height < 1) continue;

        var left = Math.floor(rect.left);
        var top = Math.floor(rect.top);
        var right = Math.floor(rect.right);
        var bottom = Math.floor(rect.bottom);

        if (top > 0 && top < viewPort[1]) {
            vert["" + top] = top;
        }
        if (bottom > 0 && bottom < viewPort[1]) {
            vert["" + bottom] = bottom;
        }
        if (right > 0 && right < viewPort[0]) {
            horz["" + right] = right;
        }
        if (left > 0 && left < viewPort[0]) {
            horz["" + left] = left;
        }
    }

    hCoords = [];
    vCoords = [];
    //TODO: 
    for (var v in vert) {
        vCoords.push(vert[v]);
    }

    for (var h in horz) {
        hCoords.push(horz[h]);
    }

    return [hCoords, vCoords];
}

function getAreaPortions() {
    var portions = {}
    var lines = getLines();

    var hCoords = lines[0];
    var vCoords = lines[1];

    for (i = 1 ; i < hCoords.length ; i++) {
        for (j = 1 ; j < vCoords.length ; j++) {
            var portion = new AreaPortion(hCoords[i - 1], vCoords[j - 1], hCoords[i], vCoords[j]);
            portions[portion.getName()] = portion;
        }
    }

    return portions;
}
4b9b3361

Ответ 1

Try

var res = [];
$("body *").each(function (i, el) {
    if ((el.getBoundingClientRect().bottom <= window.innerHeight 
        || el.getBoundingClientRect().top <= window.innerHeight)
        && el.getBoundingClientRect().right <= window.innerWidth) {
            res.push([el.tagName.toLowerCase(), el.getBoundingClientRect()]);
    };
});

jsfiddle http://jsfiddle.net/guest271314/ueum30g5/

См. Element.getBoundingClientRect()

$.each(new Array(180), function () {
    $("body").append(
    $("<img>"))
});

$.each(new Array(180), function () {
$("body").append(
$("<img>"))
});

var res = [];
$("body *").each(function (i, el) {
if ((el.getBoundingClientRect().bottom <= window.innerHeight || el.getBoundingClientRect().top <= window.innerHeight)
    && el.getBoundingClientRect().right <= window.innerWidth) {
    res.push(
    [el.tagName.toLowerCase(),
    el.getBoundingClientRect()]);
    $(el).css(
        "outline", "0.15em solid red");
    $("body").append(JSON.stringify(res, null, 4));
    console.log(res)
};
});
body {
    width : 1000px;
    height : 1000px;
}
img {
    width : 50px;
    height : 50px;
    background : navy;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>

Ответ 2

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

Обратите внимание, что некоторые элементы могут иметь преобразование 3d css (например, skew, rotate), некоторые элементы могут иметь радиус границы, а некоторые элементы могут иметь невидимый фон - если вы хотите включить эти функции также для своего элемента из пикселя, функция, то набор прямоугольников не может вам помочь, но растровое изображение может вместить все визуальные функции.

Решение для создания растрового изображения довольно просто (я полагаю, что... не проверено):

  • Создайте холст размером с видимый экран.
  • перебирать все элементы рекурсивно, сортировать по z-порядку, игнорировать скрытые
  • для каждого элемента рисуем прямоугольник в холсте, цвет прямоугольника является идентификатором элемента (например, может быть инкрементным счетчиком). Если вы хотите, вы можете изменить прямоугольник на основе визуальных особенностей элемента (перекос, поворот, радиус рамки и т.д.).
  • сохранить холст в формате без потерь, например, png not jpg
  • отправить растровое изображение как метаданные элементов на экране

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

Ответ 3

Если вы можете выбросить IE, вот простой:

function getElementVisibleRect(el) {
  return new Promise((resolve, reject) => {
    el.style.overflow = "hidden";
    requestAnimationFrame((timeStamp) => {
      var br = el.getBoundingClientRect();
      el.style.overflow = "";
      resolve(br);
    });
  });
}

Даже тогда Promises легко полиполнимы и requestAnimationFrame() работает еще в IE 8. И к 2016 году единственное, что вам нужно потрудиться, чтобы дать бедным душам более старый IE, - это четкий опыт.