I am creating a page which will contain a lot of large sized images, so naturally I want to make sure the page loads without too much trouble. I read this article here http://24ways.org/2010/speed-up-your-site-with-delayed-content
The method of deferring is as follows (pulled from page, don't mind the URL)
<div>
<h4>
<a href="http://allinthehead.com/" data-gravatar-hash="13734b0cb20708f79e730809c29c3c48">
Drew McLellan
</a>
</h4>
</div>
then later a snippet of js takes care of the image loading
$(window).load(function() {
$('a[data-gravatar-hash]').prepend(function(index){
var hash = $(this).attr('data-gravatar-hash')
return '<img width="100" height="100" alt="" src="http://www.gravatar.com/avatar.php?size=100&gravatar_id=' + hash + '">'
});
});
I don't plan on doing this for every image but definitely for some image which I don't need it to show up at page load time.
Is this the best way to go or are there better ways to achieve faster page load by deferring images?
Thanks
A little late, but in case it benefits others, there is a great article on this topic by Patrick Sexton
https://varvy.com/pagespeed/defer-images.html
He basically is suggesting the same thing, only by using tiny base 64 encoded images, he can place his image tags directly in the HTML which has the benefit of being able to control attributes like height, width, alt, etc individually. It will be a lot easier to maintain your HTML this way as opposed to creating the entire image tag in a script.
<img src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="image1.jpg" alt="image 1">
<img src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" data-src="image2.jpg" alt="image 2">
Then your script is simple and generic for all images
<script>
function init() {
var imgDefer = document.getElementsByTagName('img');
for (var i = 0; i < imgDefer.length; i++) {
if (imgDefer[i].getAttribute('data-src')) {
imgDefer[i].setAttribute('src',imgDefer[i].getAttribute('data-src'));
}
}
}
window.onload = init;
</script>
This seems to be pretty clean way of deferring images. The only potential problem is if images carry important information as "Data attributes are a new feature in HTML5".
Another option could be to put images to end of body and use CSS to position them. Personally I would stick to javascript.
Here's a version showcasing .querySelectorAll:
function swapSrcAttributes(source) {
return function(element) {
element.setAttribute('src', element.getAttribute(source));
}
}
function forEach(collection, partial) {
for (var i = 0; i < collection.length; i++) {
partial(collection[i]);
}
}
function initDeferImages() {
// for images
var deferImages = document.querySelectorAll('img[data-src]');
// or you could be less specific and remove the `img`
deferImages = document.querySelectorAll('[data-src]');
forEach(deferImages, swapSrcAttributes('data-src'));
}
window.onload = function() {
initDeferImages();
}
Here is the compatibility table for .querySelector and .querySelectorAll via https://caniuse.com/#feat=queryselector
Html
<img
width="1024"
src="https://placehold.it/64x48.jpg"
data-src-defer="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80"
alt="image 1"
/>
<img
width="1024"
src="https://placehold.it/64x48.jpg"
data-src-defer="https://images.unsplash.com/photo-1557053964-d42e8e29cb27?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1500&q=80"
alt="image 2"
/>
JS
function deferImgs() {
Array
.from(document.querySelectorAll("img[data-src-defer]"))
.forEach((element) => {
element.setAttribute("src", element.dataset.srcDefer);
});
}
window.addEventListener("load", deferImgs());
================================================================
I'm trying to comply with Farrukh's request with this edit.
I try to do my best, but English is unfortunately only the third language I speak. And I am not a language genius. :D
This js code snippet illustrates a delayed load of some big pictures.
This is not a practical implementation.
The size difference between the images is intentionally huge.
This is because the test must be illustrative.
You can monitor its operation through a browser development tool page.
F12 > Network tab > Speed settings dropdown
The ideal network speed for the test is between 1 - 3MB/s (Some slow network speed).
You may want to run the test several times, so you can see, that the order in which the images are loaded is not controlled in this case, but depends on the transmission.
Because it is not regulated, it is not possible to predict, which image will arrive first.
We load first a small image into a large placeholder.
(image: 64x48.jpg > placeholder width="1024").
The querySelectorAll() method returns a static nodelist. This list of nodes at first glance looks like an array, but it's not.
This is an array-like object:
document.querySelectorAll("img[data-src-defer]")
The Array.from() method can creates a new array instance from this object.
The forEach method can now be executed on this array.
The forEach() method executes a provided function once for each element of the array.
In this case, each element of the array is passed once to this function:
(element) => {
element.setAttribute("src", element.dataset.srcDefer);
}
and this function sets the value of the src="" attribute of the image tag, to the value of the dataset of the same image tag.
src="https://placehold.it/64x48.jpg";
data-src-defer="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80";
src = data-src-defer;
So finally:
src="https://images.unsplash.com/photo-1570280406792-bf58b7c59247?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1&auto=format&fit=crop&w=1486&q=80";
You can do it as simple as the example below:
All images have data-src attribute where you put the file path. And src attribute with a fake transparent 1x1px png image. You can also add loading attribute setted to lazy, it tells modern browsers to avoid to load immediately the images that are out of viewport (visible site zone)
<img data-src="path/to/image.jpg" loading="lazy"
src="data:image/png;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=" />
Add this script to make all images get the src attribute once your site is loaded (you need jQuery to make it work)
$(function(){
$("img[data-src]").attr("src", function(){ return $(this).data("src"); });
});
Related
I have a page where the following pattern happens quite often:
<a href="path/to/image.jpg">
<img src="path/to/image.jpg">
</a>
In order to avoid typos, I'd prefer to only have to enter the image and path once.
Is there a way (preferably using only native HTML/JS/CSS) to avoid that duplication?
Only recent browsers need to be supported.
Edited to add: there's one location in the page that has a similar but possibly conflicting pattern:
<a href="https://a.web.site/">
<img src="image.jpg">
</a>
I could get rid of it if needed.
But maybe a more robust solution would be to start from something like:
<a href="path/to/image.jpg">
IMG_LINK_TO_CREATE
</a>
and to replace a predefined pattern with the img tag, rather than the other way around.
To fit my answer to your question, I'll only use Vanilla JavaScript. Also, since it's not clear for me if you are trying to create an img from an anchor or viceversa, I am doing both for you. I'll put first the one that appears in you question title.
Identify your elements:
If you want this to work, you need to give at least a class or unique id attribute to your anchor tag in order to properly modify it later on when they are loaded into the DOM.
Generate anchor tag for an image tag
For this case, since you probably will be using multiple anchors and you'll have to do the same for every anchor you want, a class attribute with "create-link" would be enough for you to easily modify these elements directly from the DOM. Something like this would help:
<img class="create-link" src="path/to/image.jpg">
With this said, you can create a function called generateImages() which will do all the work.
function generateImages(){
let images = document.querySelectorAll(".create-link");
images.forEach(image=>{
let link = document.createElement('a'),
parent = image.parentNode,
childImage = new Image();
link.href = image.src;
link.classList.add('generated-link');
childImage.src = image.src;
link.append(childImage);
image.parentNode.removeChild(image);
parent.append(link);
});
}
And that should do it. You can now just execute it whenever you want or in the window load event.
window.onload = generateImages;
Here is a fiddle to help you visualize the overall of this method.
https://jsfiddle.net/m90b6vc5/1/
Generate image from anchor tag:
Same thing as the other one, identify your elements that you will need to use in JavaScript in the future.
The code would be a little bit easier to this, just need to retrieve the link from the anchor tag and append it to a new image element:
function generateImages(){
let a = document.querySelectorAll(".create-link");
a.forEach(element=>{
let image = new Image();
image.src = element.href;
element.append(image);
});
}
https://jsfiddle.net/m90b6vc5
You can do this. But note that this only adds the img after the page is loaded. which means the users view can be re-rendered after the page loads. You can control it to some extent by defining the expected img with-height or ratio in the .img-link class using css
$(document).ready(function(){
$('.img-link').each(function(){
$(this).append($('<img src="' + $(this).attr('href') + '" />'));
});
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>
<a href="custom-link">
<img src="custom-image.jpg" />
</a>
javascript function
function createImageStructure(number, imageArray){ var structure = "";
for(var i = 0; i < number; i++){
structure += ' <img src="'+imageArray[i]+'"> ';
} console.log(structure); }
var imageArray = [];
imageArray.push("https://pay.google.com/about/static/images/social/knowledge_graph_logo.png");
imageArray.push("https://pay.google.com/about/static/images/social/knowledge_graph_logo.png");
createImageStructure(2, imageArray);
output
<img src="https://pay.google.com/about/static/images/social/knowledge_graph_logo.png"> <img src="https://s23527.pcdn.co/wp-content/uploads/2018/05/google-photos.png">
basically, create a function, create an array, to have image paths, this will help to create HTML structure with multiple images.
if need more help please let me know, i will fix this, if you want just one image source path for all img tags
While I really don't want to encourage you to do this with client-side code, I will at least suggest you use code that generates links instead of code that generates images. This way, the website still shows images if the JS doesn't run.
The simplest way to do this is to add a class to all images which you want to automatically wrap in a link, such as "auto-link", and then run this code:
for (const img of document.querySelectorAll(".auto-link")) {
const link = document.createElement("a");
link.href = img.src;
img.parentElement.replaceChild(link, img);
link.appendChild(img);
}
You can put this in an "domready" or "load" event listener, or just in a script tag at the end of the page.
Note that pretty much all browsers have a "view image" option in their context menu, so there's no reason to do this. You shouldn't introduce a dependency on JavaScript, which slows down execution and wont work if you disable JS or use a screen reader. Instead, features like these ought to be done server-side or as a compilation step.
A good way to encapsulate your html and reuse it elsewhere is React.
function AImg({ href, src }) {
return <a href={ href || src }>
<img src={ src }/>
</a>;
}
ReactDOM.render(
<div>
<AImg src="https://placecage.com/./200/200" />
<AImg src="https://placecage.com/c/200/200" href="https://placecage.com"/>
<AImg src="https://placecage.com/g/200/200" />
</div>,
document.getElementById('aimg_container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="aimg_container"></div>
React.js is good way to go. If you want to still use ES6 only you can use also backticks. Add links to images and links in an array and in a for of loop create links with image. Something like:
const urls = ['1', '2', '3'];
const images = ['a','b','c'];
let links = [];
for (let index of urls.keys()) {
links.push(`
<img src="${images[index]}" />
`);
}
Adding elements to the DOM can be expensive. I would not be adding a tags via javascript. Keep your HTML mostly as is, but leave the href attribute empty for the links you want to populate.
I've also given you the option of populating the image source based on the href. This is not as good as the image has to be loaded after the page is rendered.
//Wait for everything to be loaded
document.addEventListener("DOMContentLoaded", function(){
//Find a tags with empty hrefs
let emptyAs = document.querySelectorAll("a[href='']");
emptyAs.forEach((a) => {
//Update href based on image src
a.href = a.querySelector("img").src;
});
//Alternatively Find images with empty src
let emptyImgs = document.querySelectorAll("img[src='']");
emptyImgs.forEach((img) => {
img.src = img.parentNode.href;
});
});
<a href="">
<img src="https://fillmurray.com/200/200" />
</a>
<a href="">
<img src="https://fillmurray.com/100/100" />
</a>
<a href="https://fillmurray.com/300/300">
<img src="" />
</a>
<a href="http://www.google.com">
<img src="https://fillmurray.com/400/400" />
</a>
Note forEach has no IE support for a node list: https://developer.mozilla.org/en-US/docs/Web/API/NodeList/forEach
Hey I'm trying to make a function that prints out the last character of the id of the element it's called from. This is simply a test before I move forward with my actual plans but I'm stuck here. The Issue I'm getting is that I get an error saying that my parameter is 'undefined', so there is not value being passed, or the value being passed isn't a string and thus string operations (charAt) won't work on it. I have included code. Thank you.
My webpage have an undertermined amount of modal images which , when clicked, open a slideshow album of images. I got this working for 1 image then realized that to have it work for an undetermined amount of slideshows of undetermined size I should make a function that fills the slideshow div. I planned to have each modal image to have an id of "modal-1, modal-2...etc" and have a bunch of arrays with the images each named similarly "slides_1 ,slides_2...etc" and if the last character of both match up, then it will populate the slideshow with the images in the array. This way If i need to add another modal image all i need to do is give it the appropriate id and add an array of its images.this is a ,past question which sort of illustrates that larger goal.
html:
<body id="modal-2" onload="fillSlides(this.id)">
<h2 id="title" style="text-align:center"></h2>
<div class="row">
<div class="column">
<img id="modal-1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" onclick="openModal();currentSlide(1);fillSlides(this.id);" class="hover-shadow cursor">
</div>
</div>
Javascript:
function fillSlides(modalID){
var slides_1 = ["Images/LS_01.jpg", "Images/LS_02.jpg", "Images/LS_03.jpg", "Images/LS_04.jpg" ]
var modalI = modalID;
var lastCharM = modalI.charAt(modalI.length - 1);
document.getElementById("title").innerHTML = "the last char is" + lastCharM;
}
The load event is actually a window event, not actually an event on body, so this within your onload-attribute-style handler text is window. To access body, use document.body instead of this:
<body id="modal-2" onload="fillSlides(document.body.id)">
Live example on JSBin (since Stack Snippets want to supply the body markup for you).
In general, I'd recommend avoiding onxyz-attribute-style event handlers. For one thing, other than methods on the element or (sometimes) it's containing element, the only functions they can call have to be globals, and the global namespace on browsers is already really crowded.
Instead, I'd recommend at least using the onxyz properties on elements and on window. In this case, that would look like this in your JavaScript code:
window.onload = function() { loadSlides(document.body.id); };
But even better would be to use modern (DOM2+) event handling via addEventListener (with fallback to Microsoft's proprietary attachEvent if you need to support obsolete versions of IE; details here):
window.addEventListener("load", function() {
loadSlides(document.body.id);
};
I would approach this using data attributes instead of parsing a string. This will allow you to use a class instead of id's on your HTML img elements, and then determine which element was clicked on via the data attribute.
HTML
<img data-num="1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" class="hover-shadow cursor img">
JS
var classname = document.getElementsByClassName("img");
var myFunction = function() {
var attribute = this.dataset.num;
//attribute has the data-num value
};
for (var i = 0; i < classname.length; i++) {
classname[i].addEventListener('click', myFunction, false);
}
I'm also binding the click event in JS rather than an inline onclick in order to preserve separation of concerns (not mixing JS and HTML).
Update, in response to comment by kakamg0:
Using document.querySelectorAll('img[data-num]') would omit the need to use a class for targeting.
HTML
<img data-num="1" src="https://www.yosemitehikes.com/images/wallpaper/yosemitehikes.com-bridalveil-winter-1200x800.jpg" style="max-width:100%" class="hover-shadow cursor">
JS
var images = document.querySelectorAll('img[data-num]');
var myFunction = function() {
var attribute = this.dataset.num;
//attribute has the data-num value
};
for (var i = 0; i < images.length; i++) {
images[i].addEventListener('click', myFunction, false);
}
I'm trying to make some basic image slider as a part of JS excersises, and I've noticed something weird when I ran this script.
The source of an image stored in array under the 0 index is changing when I'm trying to change source image in html code with 'setAttribute' method, so when I want to get back from fifth image to one, this proto-slider (nothing is sliding at the moment) is stuck at second image.
My files are named 0.jpg - 5.jpg.
I've preloaded the images to be displayed smoothly without flicker and I'm displaying one image after the page loads, and then I'm trying to change source of this image to source of next image in array of images. Is this the right way to do it?
Here's the code:
var images = new Array(5);
var i=0;
function addImages(){
for (var j=0;j<images.length;j++){
images[j] = new Image();
images[j].src = j+".jpg";
console.log(images[j].src);
}
var article = document.getElementById("article");
article.insertBefore(images[0], document.getElementById("nav"));
}
function next(){
if (i<images.length-1){
document.getElementsByTagName('img')[0].setAttribute('src',images[++i].src);
console.log(images[0].src);
}}
function prev(){
if (i>0){
document.getElementsByTagName('img')[0].setAttribute('src', images[--i].src);
console.log(images[0].src);
}
}
What is the best way to do such a this as dynamically changing image sources?
I've thought about this:
article.removeChild(document.getElementsByTagName("img")[0]);
article.insertBefore(images[++i], document.getElementById("nav"));
I want to know if this isn't considered as a bad practice and what are the other ways to do it in single line of code.
Thanks in advance and sorry for grammar mistakes.
You could just insert empty image
<img id="someid">
and then use document.getElementById("someid").src=images[0].src
If you'd like to be sure that images[0] is completely loaded, use onload function, like:
images[0].onload=function()
{
document.getElementById("someid").src=images[0].src;
}
I am new to casperjs, and as far as I have learned so far, there are only two click methods that can trigger a mouse action:
click() requires a selector
clickLabel() requires "label" between tags
The website I am dealing with right now has dynamic "tabs", by clicking each tab, a javascript submit is triggered, there is no "class", "id" or "label" associated with each tab, except for "pic" element:
<a href="javascript:submitTab('search6')" tabindex="6">
<img src="image6off.gif" name="imag6" height="6" hspace="0" vspace="0" border="0" onmouseover="nbGroup('over','imag6','image6on.gif','image6on.gif',1);" onmouseout="nbGroup('out');" onclick="nbGroup('down','group1','imag6','image6off.gif',1); submitTab('search6')" alt="New Search">
</a>
I tried to use clickLabel() but failed.
YES, I can use XPath, however the problem is the number of tabs is dynamic depending on the available information for each record, so in this case "new search" could be tab 6 for this record but tab 4 in another, tab 8 in yet another.
YES, I could try to write a "loop" to loop through all available tabs, potentially, however, if there is one method of click which combine the
waitForResource()
that would be great, since I can use the "image6on.gif" to tell the program which image or tab to click, apparently, for this website, I found out that each different javascript submit tab program is uniquely associated with one "image#on/off.gif"
I hope some contributor for casperjs can easily implement this method to deal this kind of situation.
Not entirely sure if this is what you want, but you can get the tab based on the tabindex attribute with:
casper.click("a[tabindex='6']");
Edit: Hack I threw together based on your comment below:
casper.thenEvaluate(function() {
var attr = document.querySelector('img[alt="New Search"]').parentNode.getAttribute('tabindex');
__utils__.click('a[tabindex="' + attr + '"]');
});
casper.thenEvaluate() allows you to execute javascript on the remote page.
__utils__ is injected into each page loaded as an extra set of functions that you can use.
I'm not a contributor to CasperJS. From my point of view the clickLabel function is already too much. I cannot remember that I actually used it, because most of the time there is something that prevents an exact string match.
You are right, it is a valid argument to add a new click function to CasperJS. In my opinion it is better to use the provided XPath capability to do that. You can even create the function for your use:
casper.clickByImg = function(imgRes){
var x = require('casper').selectXPath;
this.click(x("//a/img[contains(#href,"+imgRes+")]/.."));
return this;
};
See: minimal overhead.
You can even go this far as to match the image by a regular expression with more overhead.
casper.clickByImgRegexp = function(regexp){
var hrefs = this.getElementsAttribute("a > img", "href");
for(var i = 0; i < hrefs.length; i++) {
if (hrefs[i].match(regexp)) {
this.clickByImg(hrefs[i]);
break;
}
}
return this;
};
So, I'm using this lovely image swap fellow:
jQuery Image Swap Gallery
You can see a demo of it in action on my site here: http://hannahnelsonteutsch.com/v2/art-piece.php
The problem is, with the #main_image img
<div id="main_image" class="grid_7">
<img class="wide" src="zImages/arrows-2.jpg" />
</div>
note the class="wide". there are two classes that need to be applied dynamically to the images, once they're "swapped": "wide" and "tall", based on whether we're using an image that is in portrait or landscape.
The nice thing, which I have a hunch that a superior coder could make use of, is that the thumbnails for each image already have the correct class assigned to them:
<img class="tall" src="zImages/arrows-1.jpg" />
<img class="wide" src="zImages/arrows-2.jpg" />
<img class="wide" src="zImages/arrows-3.jpg" />
<img class="tall" src="zImages/arrows-4.jpg" />
<img class="tall" src="zImages/arrows-5.jpg" />
Here is the jQuery that is generating the image-swap:
$(document).ready(function() {
// Image swap on hover
$("#details img").hover(function(){
$('#main_image img').attr('src',$(this).attr('src').replace());
// This is my failed attempt to assign
// the correct class to the #main_img img:
$('#main_image img').attr('class',$('#details img').attr('class').replace(this));
});
// Image preload
var imgSwap = [];
$("#details img").each(function(){
imgUrl = this.src.replace();
imgSwap.push(imgUrl);
});
$(imgSwap).preload();
});
$.fn.preload = function() {
this.each(function(){
$('<img/>')[0].src = this;
});
}
Note two things:
The original tutorial has this line of code:
$('#main-img').attr('src',$(this).attr('src').replace('thumb/', ''));
which i've replaced with this:
$('#main_image img').attr('src',$(this).attr('src').replace());
the key being the .replace('thumb/', '') vs .replace().
We're using CSS to shrink the images which significantly speeds up image load times upon hover. i.e. the images have already been loaded because we're using the same image for the thumbs and main image.
my own attempt to answer this question has not worked
$('#main_image img').attr('class',$('#details img').attr('class').replace(this));
So, that's where I'm at. Anyone have any ideas to solve this ?
Thanks so much for your time.
Cheers,
Jon
You need to use the $(this) selector so jquery knows which image you are hovering over. Also you should probably use mouseenter instead of hover as you only need to verify the mouse position once. Finally, got rid of replace() in your function as that wasn't doing anything. By using attr and changing the src or class you are replacing the value automatically. Here's a draft I set up of your page: http://jsfiddle.net/AjBDg/2/
Using $('#main_image img').attr('class',$(this).attr('class')); should be enough.
See: http://jsfiddle.net/DpvFX/