using .each() to iterate over selections for HTML5 <figure> and <figcaption>
Greetings all,
I've written a script to create HTML5 image captions from regular image tags using <figure>
and <figcaption>
.
My CMS uses FCKEditor, which always places embedded images inside of paragraphs. So my script builds a <figcaption>
around the image and then moves it outside of the paragraph (see html5, figure/figcaption inside a paragraph gives unpredictable output)).
The script I wrote works, but it traverses the DOM twice 'cause I couldn't figure out a way to traverse the DOM only once. I'd appreciate if someone better versed at JQuery could offer some suggestions on how to simplify/improve the script.
Thanks, -NorthK
// use like this:
// <img cl开发者_StackOverflow中文版ass="caption" src="http://placehold.it/350x150" alt="Sample image caption" />
//
$(document).ready(function() {
// iterate over each element with img.caption
$('img.caption').each(function() {
var classList = $(this).attr('class'); // grab the image's list of classes, if any
$(this).wrap('<figure class="' + classList + '"></figure>'); // wrap the <img> with <figure> and add the saved classes
$(this).after('<figcaption>' + $(this).attr('alt') + '</figcaption>'); // add the caption
$(this).removeAttr('class'); // remove the classes from the original <img> element
});
// now iterate over each figure.caption we built, and relocate it to before its closest preceding paragraph
$('figure.caption').each(function() {
$(this).parent('p').before($(this));
});
})
As you wrap each element, you can save the wrapper in an array, then process the array rather than re-traversing the DOM:
$(document).ready(function() {
var wrappers = [];
// iterate over each element with img.caption
$('img.caption').each(function() {
var $this = $(this);
var classList = this.className; // grab the image's list of classes, if any
$this.wrap('<figure class="' + classList + '"></figure>'); // wrap the <img> with <figure> and add the saved classes
// Remember the wrapper here:
wrappers.add($this.parent());
$this.after('<figcaption>' + $(this).attr('alt') + '</figcaption>'); // add the caption
$this.removeAttr('class'); // remove the classes from the original <img> element
});
// now iterate over each figure.caption we built, and relocate it to before its closest preceding paragraph
$.each(wrappers, function(index, item) {
$(item).parent('p').before(item);
});
// And if you're done with it, release it:
wrappers = null;
});
Here's a simplified example:
HTML:
<p><img class='caption' src='http://www.gravatar.com/avatar/6d8ebb117e8d83d74ea95fbdd0f87e13?s=48&d=identicon&r=PG'></p>
<p><img class='caption' src='http://www.gravatar.com/avatar/ca3e484c121268e4c8302616b2395eb9?s=48&d=identicon&r=PG'</p>
JavaScript:
jQuery(function($) {
var wrappers = [];
$("img.caption").each(function() {
var $this = $(this);
$this.wrap("<figure class='" + this.className + "'>");
wrappers.push($this.parent('figure'));
});
$.each(wrappers, function(index, item) {
$(item).addClass("foo");
});
wrappers = null;
});
Live copy
Off-topic: You seem to be interested in efficiency, so I'll mention: Every call to $(this)
requires multiple function calls and a memory allocation. Rather than doing it repeatedly, do it once on each loop and cache the result. I've done that in the above as an example. Constantly writing $(this)
in the same function isn't ideal from a performance perspective, although in 99% of cases, it doesn't matter. If you're dealing with a lot of elements, it does.
You can use the unwrap
method to get rid of the <p>
in the first iteration. You can also use chaining to simplify the syntax.
$('.caption').each(function() {
$(this)
.unwrap() // ditch the conflicting pararaph parent wrapper
.wrap('<figure class="' + $(this).attr('class') + '" />') // wrap the figure tag instead, jQuery can handle a self closing tag
.after('<figcaption>' + $(this).attr('alt') + '</figcaption>') // add the caption
.removeAttr('class'); // remove the classes from the original <img> element
});
http://jsfiddle.net/pxfunc/AGgL2/
精彩评论