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;
}