可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
I want to write a browser (Chrome/FF) extension that needs to select an element on a web page. I would like it to behave like Firebug's element inspector does. You click the inspect arrow and you can then hover/highlight elements. When you click on the element you want, the element is inspected. I'm just interested in the code to allow a user to select an element - not in actually inspecting it or anything similar.
Because I'm writing an extension, it might be nice if you could provide non-jQuery/Prototype/etc.. code so I don't have to distribute that.
回答1:
I wrote an implementation of this using jQuery as a component of another project. The source and documentation are available here: https://github.com/andrewchilds/jQuery.DomOutline
回答2:
I have recently required such a feature for a project I was working on, turned out that I had to use for sides to create a box because otherwise the event.target
when you move the mouse would end up being the selector, and if I were to use z-index: -1
it would be a bit fishy when you have a lot of elements that overlap...etc.
Here is a version that I have converted from my project for your benefit, it involves jQuery but it is extremely simple to convert to vanilla as only the mousemove
& css
methods from jQuery are used.
Step by step instructions.
First create the 5 HTMLElements that are required.
<div id="selector">
<div id="selector-top"></div>
<div id="selector-left"></div>
<div id="selector-right"></div>
<div id="selector-bottom"></div>
</div>
Secondly create a mousemove
event on the document
(or your container)
$(document).mousemove(function(event) { ... });
Then inside the mousemove
we will do some basic checking to prevent selecting the HTML, BODY, selector
var id = event.target.id, tagName = event.target.tagName;
if(id.indexOf('selector') !== -1 || tagName === 'BODY' || tagName === 'HTML') {
return;
}
Then we need to create a object to store our elements like so.
var elements = {
top: $('#selector-top'),
left: $('#selector-left'),
right: $('#selector-right'),
bottom: $('#selector-bottom')
};
After that we store some variables that hold some information about the target element like so.
var $target = event.target;
targetOffset = $target.getBoundingClientRect(),
targetHeight = targetOffset.height,
targetWidth = targetOffset.width;
Then all we do is calculate the position & height for all 4 sides of the selector like so.
elements.top.css({
left: (targetOffset.left - 4),
top: (targetOffset.top - 4),
width: (targetWidth + 5)
});
elements.bottom.css({
top: (targetOffset.top + targetHeight + 1),
left: (targetOffset.left - 3),
width: (targetWidth + 4)
});
elements.left.css({
left: (targetOffset.left - 5),
top: (targetOffset.top - 4),
height: (targetHeight + 8)
});
elements.right.css({
left: (targetOffset.left + targetWidth + 1),
top: (targetOffset.top - 4),
height: (targetHeight + 8)
});
All of the +aFewPixels
is just a little optimization so that there is like 2px
gap in between the selector and the target.
For the CSS
this is what I have come up with.
#selector-top, #selector-bottom {
background: blue;
height:3px;
position: fixed;
transition:all 300ms ease;
}
#selector-left, #selector-right {
background: blue;
width:3px;
position: fixed;
transition:all 300ms ease;
}
The transition
gives the selector a very nice sliding effect.
Try out a demo http://jsfiddle.net/rFc8E/9/
Note: This also works for transform: scale(2);
eg. when a element is scaled in size.
Edit: I've just updated this, I noticed that the elements
object was inside the event handler, I've moved it outside in the demo, this is quite an important performance improvement because now, the elements
object is only created once instead of Hundreds of Thousands if not millions of times inside the mousemove
event.
回答3:
One simple way to do it is to use an outline instead of a border:
.highlight { outline: 4px solid #07C; }
Just add and remove that class to any element you want to select/deselect (code below is not properly tested):
document.body.addEventListener("mouseover", function(e) {
e.stopPropagation();
e.target.addEventListener("mouseout", function (e) {
e.target.className = e.target.className.replace(new RegExp(" highlight\\b", "g"), "");
});
e.target.className += " highlight";
});
Since you are using an outline, (which is supported by Chrome) instead of a border, elements will not jump around. I'm using something similar in my EasyReader Extension.
回答4:
I ended up asking in the Firebug group and got some great help:
http://groups.google.com/group/firebug/browse_thread/thread/7d4bd89537cd24e7/2c9483d699efe257?hl=en#2c9483d699efe257
回答5:
Also check this one out:
http://rockingcode.com/tutorial/element-dom-tree-jquery-plugin-firebug-like-functionality/
I found it pretty insightful.. and there's a demo here:
http://rockingcode.com/demos/elemtree/
Hope this helps.
回答6:
There was a similar question asked on Stackoverflow and it had lots of good answers:
Does anyone know a DOM inspector javascript library or plugin?
For those who are looking for a quick and dirty solution:
http://userscripts.org/scripts/review/3006 is the easiest. Just put the code within <script></script>
tags and you are good to go.
https://github.com/josscrowcroft/Simple-JavaScript-DOM-Inspector/blob/master/inspector.js is slightly better and still very easy to integrate in.
For a more sophisticated element inspector, you might want to check out the SelectorGadget as pointed by Udi. The inspector selection code is in http://www.selectorgadget.com/stable/lib/interface.js
回答7:
What you need to do is to create 4 elements for the highlighting. They will form an empty square, and so your mouse events are free to fire. This is similar to this overlay example I've made.
The difference is that you only need the four elements (no resize markers), and that the size and position of the 4 boxes are a bit different (to mimick the red border). Then you can use event.target
in your event handler, because it gets the real topmost element by default.
Another approach is to hide the exra element, get elementFromPoint
, calculate then put it back.
They're faster than light, I can tell you. Even Einstein would agree :)
1.) elementFromPoint overlay/borders - [Demo1] FF needs 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.) mouseover borders - [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);
}
回答8:
A very basic implementation can be done very easily without jQuery using .onmouseover
and e.target
:
var last,
bgc;
document.onmouseover = function(e) {
var elem = e.target;
if (last != elem) {
if (last != null) {
last.classList.remove("hovered");
}
last = elem;
elem.classList.add("hovered");
}
}
With the CSS below if you want the children to change background as well:
.hovered,
.hovered * {
cursor: pointer;
color: black;
background-color: red;
}
Demo
If you want to select elements only near the edges (or select the parent near the edges and the element itself everywhere else) you could use .getBoundingClientRect
.
var last;
window.addEventListener("mousemove", function(e) {
if(last) {
last.style.background = ''; // empty is enough to restore previous value
}
var elem = e.target;
if(elem === document.body || elem === document.documentElement) {
return;
}
var bb = elem.getBoundingClientRect();
var xr = e.pageX - bb.left; // x relative to elem
var yr = e.pageY - bb.top; // y relative to elem
var ew = 10; // edge width
if(
xr <= ew
|| xr >= bb.width - ew
|| yr <= ew
|| yr >= bb.height - ew
){
elem.style.background = 'red';
last = elem;
}
});
Paired with some borders, this can be pretty usable for selection. Demo
回答9:
Here is a library that written in pure javascript as an alternative.
TheRoom JS: https://github.com/hsynlms/theroomjs
// theroom information template for target element
var template="";
template += "<div id=\"theroom-info\">";
template += " <span id=\"theroom-tag\"><\/span>";
template += " <span id=\"theroom-id\"><\/span>";
template += " <span id=\"theroom-class\"><\/span>";
template += "<\/div>";
template += "";
template += "<style>";
template += " #theroom-info {";
template += " position: fixed;";
template += " bottom: 0;";
template += " width: 100%;";
template += " left: 0;";
template += " font-family: \"Courier\";";
template += " background-color: #ffffff;";
template += " padding: 10px;";
template += " color: #333333;";
template += " text-align: center;";
template += " box-shadow: 0px 4px 20px rgba(0,0,0,0.3);";
template += " }";
template += "";
template += " #theroom-tag {";
template += " color: #C2185B;";
template += " }";
template += "";
template += " #theroom-id {";
template += " color: #5D4037;";
template += " }";
template += "";
template += " #theroom-class {";
template += " color: #607D8B;";
template += " }";
template += "<\/style>";
var options = {
template: template,
showInfo: true
};
// initialize
theRoom.start(options);
codepen demo