2011-01-17 3 views
12

По-прежнему пытаюсь ответить this question, и я думаю, что, наконец, я нашел решение, но оно работает слишком медленно.Как эффективно выделить элемент под курсором мыши с наложением?

var $div = $('<div>') 
    .css({ 'border': '1px solid red', 'position': 'absolute', 'z-index': '65535' }) 
    .appendTo('body'); 

$('body *').live('mousemove', function(e) { 
    var topElement = null; 
    $('body *').each(function() { 
     if(this == $div[0]) return true; 
     var $elem = $(this); 
     var pos = $elem.offset(); 
     var width = $elem.width(); 
     var height = $elem.height(); 
     if(e.pageX > pos.left && e.pageY > pos.top 
      && e.pageX < (pos.left + width) && e.pageY < (pos.top + height)) { 
      var zIndex = document.defaultView.getComputedStyle(this, null).getPropertyValue('z-index'); 
      if(zIndex == 'auto') zIndex = $elem.parents().length; 
      if(topElement == null || zIndex > topElement.zIndex) { 
       topElement = { 
        'node': $elem, 
        'zIndex': zIndex 
       }; 
      } 

     } 
    }); 
    if(topElement != null) { 
     var $elem = topElement.node; 
     $div.offset($elem.offset()).width($elem.width()).height($elem.height()); 
    } 
}); 

Это в основном перебирает все элементы на странице и находит самый верхний элемент под курсором.

Возможно ли, что я смогу использовать квад-дерево или что-то еще и сегментировать страницу, чтобы цикл работал быстрее?

+0

'$ (this) .closest ('body> *')' should g Являюсь вам самым главным предком 'этого', это то, что вы хотите?или ваш макет, чтобы элементы не всегда находились внутри их предка? – tobyodavies

+2

Есть ли причина, по которой вы не можете просто использовать 'e.currentTarget' вместо цикла для поиска элемента? – Guffa

+0

@toby: Это расширение Chrome. Может быть запущен на любой странице. Не знаю, как будет выглядеть разметка. Под «самым верхним» я имею в виду самый высокий z-индекс ... там могут быть менее глубоко вложенные элементы, которые стилизованы, чтобы появляться над другими. – mpen

ответ

18

Возможно ли, что я мог бы использовать квад-дерево или что-то еще и сегментировать страницу, чтобы цикл работал быстрее?

Просто сделайте шаг назад, осознайте, насколько мала проблема, и что чем сложнее вы попытаетесь выполнить более сложный ответ, который вы будете использовать.

Теперь вам нужно создать 4 элемента для выделения. Они образуют пустой квадрат, и поэтому ваши события мыши могут стрелять. Это похоже на это overlay example, которое я сделал.

Разница в том, что вам нужны только четыре элемента (без маркеров изменения размера), а размер и положение четырех ящиков немного отличаются (чтобы имитировать красную рамку). Затем вы можете использовать event.target в обработчике событий, поскольку по умолчанию он получает реальный верхний элемент.

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

Они быстрее света, я могу вам сказать. Даже Эйнштейн согласен :)

1.) elementFromPoint наложения/границы - [Demo1]FF нужен v3.0 +

var box = $("<div class='outer' />").css({ 
    display: "none", position: "absolute", 
    zIndex: 65000, background:"rgba(255, 0, 0, .3)" 
}).appendTo("body"); 

var mouseX, mouseY, target, lastTarget; 

// in case you need to support older browsers use a requestAnimationFrame polyfill 
// e.g: https://gist.github.com/paulirish/1579671 
window.requestAnimationFrame(function frame() { 
    window.requestAnimationFrame(frame); 
    if (target && target.className === "outer") { 
     box.hide(); 
     target = document.elementFromPoint(mouseX, mouseY); 
    } 
    box.show(); 

    if (target === lastTarget) return; 

    lastTarget = target; 
    var $target = $(target); 
    var offset = $target.offset(); 
    box.css({ 
     width: $target.outerWidth() - 1, 
     height: $target.outerHeight() - 1, 
     left: offset.left, 
     top: offset.top 
    }); 
}); 

$("body").mousemove(function (e) { 
    mouseX = e.clientX; 
    mouseY = e.clientY; 
    target = e.target; 
}); 

2.) наведения мыши границы - [Demo2 ]

var box = new Overlay(); 

$("body").mouseover(function(e){ 
    var el = $(e.target); 
    var offset = el.offset(); 
    box.render(el.outerWidth(), el.outerHeight(), offset.left, offset.top); 
});​ 

/** 
* This object encapsulates the elements and actions of the overlay. 
*/ 
function Overlay(width, height, left, top) { 

    this.width = this.height = this.left = this.top = 0; 

    // outer parent 
    var outer = $("<div class='outer' />").appendTo("body"); 

    // red lines (boxes) 
    var topbox = $("<div />").css("height", 1).appendTo(outer); 
    var bottombox = $("<div />").css("height", 1).appendTo(outer); 
    var leftbox = $("<div />").css("width", 1).appendTo(outer); 
    var rightbox = $("<div />").css("width", 1).appendTo(outer); 

    // don't count it as a real element 
    outer.mouseover(function(){ 
     outer.hide(); 
    });  

    /** 
    * Public interface 
    */ 

    this.resize = function resize(width, height, left, top) { 
     if (width != null) 
     this.width = width; 
     if (height != null) 
     this.height = height; 
     if (left != null) 
     this.left = left; 
     if (top != null) 
     this.top = top;  
    }; 

    this.show = function show() { 
     outer.show(); 
    }; 

    this.hide = function hide() { 
     outer.hide(); 
    };  

    this.render = function render(width, height, left, top) { 

     this.resize(width, height, left, top); 

     topbox.css({ 
      top: this.top, 
      left: this.left, 
      width: this.width 
     }); 
     bottombox.css({ 
      top: this.top + this.height - 1, 
      left: this.left, 
      width: this.width 
     }); 
     leftbox.css({ 
      top: this.top, 
      left: this.left, 
      height: this.height 
     }); 
     rightbox.css({ 
      top: this.top, 
      left: this.left + this.width - 1, 
      height: this.height 
     }); 

     this.show(); 
    };  

    // initial rendering [optional] 
    // this.render(width, height, left, top); 
} 
+0

Это то, что кто-то предложил и в другой теме. Я действительно хотел, чтобы полупрозрачный красный прямоугольник, наложенный ... должен был изменить стиль, прежде чем я разместил это. Но так или иначе, разве это не сломается, когда вы намытесь над границами? – mpen

+0

Просто удалите/скройте пустой квадрат 'onmouseover', затем следующее движение мыши вызывает правильный элемент. Это будет незаметно для пользователя. Позвольте мне сделать демо для вас ... – galambalazs

+0

Я обновил первую версию для поддержки полупрозрачного красного наложения. Теперь это именно то, что вы хотели. – galambalazs

1

Во-первых, я не думаю, что делать $('body *').live это очень хорошая идея, это, кажется, очень дорого (думают о виде расчета браузер должен делать каждый раз, когда вы перемещаете мышь)

Это сказал, здесь оптимизированная версия, которая игнорирует этот аспект

var $div = $('<div>') 
    .css({ 'border': '1px solid red', 'position': 'absolute', 'z-index': '65535' }) 
    .appendTo('body'); 

$('body *').live('mousemove', function(e) { 
    var topElement = null; 
    var $bodyStar = $('body *'); 
    for(var i=0,elem;elem=$bodyStar[i];i++) { 
     if(elem == $div[0]) continue; 
     var $elem = $(elem); 
     var pos = $elem.offset(); 
     var width = $elem.width(); 
     var height = $elem.height(); 
     if(e.pageX > pos.left && e.pageY > pos.top && e.pageX < (pos.left + width) && e.pageY < (pos.top + height)) { 
      var zIndex = document.defaultView.getComputedStyle(this, null).getPropertyValue('z-index'); 
      if(zIndex == 'auto') zIndex = $elem.parents().length; 
      if(topElement == null || zIndex > topElement.zIndex) { 
       topElement = { 
        'node': $elem, 
        'zIndex': zIndex 
       }; 
      } 

     } 
    } 
    if(topElement != null) { 
     var $elem = topElement.node; 
     $div.offset($elem.offset()).width($elem.width()).height($elem.height()); 
    } 
}); 

На будущее, никогда не использовать jQuerys циклических механизмов, если вам нужна производительность. Все они строят вокруг вызовов функций для каждой итерации, что очень медленно по сравнению с обычным циклом, поскольку инициирование стека вызовов, которое происходит при вызове функции, является огромной накладной для большинства операций итерации, которые вам нужно выполнить.

Код обновлен, чтобы исправить ошибки и включить динамически вставленные элементы.

+0

Прежде всего, лучше использовать 'addClass() 'чем' .css() '. Во-вторых, могу ли я спросить, когда закончится цикл цикла 'for'? – Reigel

+2

Это закончится, когда $ bodystar [i] получит undefined, что он будет делать, когда я достигнет длины массива. Это самый эффективный способ петли над массивом, который, как вы знаете, не содержит значений, которые будут оцениваться как ложные. –

+0

фактически использует while (i--) быстрее, но это совершенно другое игровое поле :) –

Смежные вопросы