How to Lazy Load div background images - javascript

As many of you know it is widely used to lazy load images.
Now i want to use this as lazy load div background images.
How can i do that ?
I am currently able to use http://www.appelsiini.net/projects/lazyload that plugin
So i need to modify it in a way that it will work with div backgrounds
Need help. Thank you.
The below part i suppose lazy loads images
$self.one("appear", function() {
if (!this.loaded) {
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
$("<img />")
.bind("load", function() {
$self
.hide()
.attr("src", $self.data(settings.data_attribute))
[settings.effect](settings.effect_speed);
self.loaded = true;
/* Remove image from array so it is not looped next time. */
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp);
if (settings.load) {
var elements_left = elements.length;
settings.load.call(self, elements_left, settings);
}
})
.attr("src", $self.data(settings.data_attribute));
}
});
Jquery plugin lazy load

First you need to think off when you want to swap. For example you could switch everytime when its a div tag thats loaded. In my example i just used a extra data field "background" and whenever its set the image is applied as a background image.
Then you just have to load the Data with the created image tag. And not overwrite the img tag instead apply a css background image.
Here is a example of the code change:
if (settings.appear) {
var elements_left = elements.length;
settings.appear.call(self, elements_left, settings);
}
var loadImgUri;
if($self.data("background"))
loadImgUri = $self.data("background");
else
loadImgUri = $self.data(settings.data_attribute);
$("<img />")
.bind("load", function() {
$self
.hide();
if($self.data("background")){
$self.css('backgroundImage', 'url('+$self.data("background")+')');
}else
$self.attr("src", $self.data(settings.data_attribute))
$self[settings.effect](settings.effect_speed);
self.loaded = true;
/* Remove image from array so it is not looped next time. */
var temp = $.grep(elements, function(element) {
return !element.loaded;
});
elements = $(temp);
if (settings.load) {
var elements_left = elements.length;
settings.load.call(self, elements_left, settings);
}
})
.attr("src", loadImgUri );
}
the loading stays the same
$("#divToLoad").lazyload();
and in this example you need to modify the html code like this:
<div data-background="http://apod.nasa.gov/apod/image/9712/orionfull_jcc_big.jpg" id="divToLoad" />​
but it would also work if you change the switch to div tags and then you you could work with the "data-original" attribute.
Here's an fiddle example: http://jsfiddle.net/dtm3k/1/

EDIT: the post from below is from 2012 and absolete by now!
I do it like this:
<div class="lazyload" style="width: 1000px; height: 600px" data-src="%s">
<img class="spinner" src="spinner.gif"/>
</div>
and load with
$(window).load(function(){
$('.lazyload').each(function() {
var lazy = $(this);
var src = lazy.attr('data-src');
$('<img>').attr('src', src).load(function(){
lazy.find('img.spinner').remove();
lazy.css('background-image', 'url("'+src+'")');
});
});
});

Mid last year 2020 web.dev posted an article that shared some new ways to do this with the the new IntersectionObserver which at the time of writing this answer is supported in all major browsers. This will allow you to use a very light weight background image, or background color placeholder while you wait for the image to come to the edge of the viewport and then it is loaded.
CSS
.lazy-background {
background-image: url("hero-placeholder.jpg"); /* Placeholder image */
}
.lazy-background.visible {
background-image: url("hero.jpg"); /* The final image */
}
Javascript
document.addEventListener("DOMContentLoaded", function() {
var lazyBackgrounds = [].slice.call(document.querySelectorAll(".lazy-background"));
if ("IntersectionObserver" in window) {
let lazyBackgroundObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
lazyBackgroundObserver.unobserve(entry.target);
}
});
});
lazyBackgrounds.forEach(function(lazyBackground) {
lazyBackgroundObserver.observe(lazyBackground);
});
}
});

I've found this on the plugin's official site:
<div class="lazy" data-original="img/bmw_m1_hood.jpg" style="background-image: url('img/grey.gif'); width: 765px; height: 574px;"></div>
$("div.lazy").lazyload({
effect : "fadeIn"
});
Source: http://www.appelsiini.net/projects/lazyload/enabled_background.html

I've created a "lazy load" plugin which might help. Here is the a possible way to get the job done with it in your case:
$('img').lazyloadanything({
'onLoad': function(e, LLobj) {
var $img = LLobj.$element;
var src = $img.attr('data-src');
$img.css('background-image', 'url("'+src+'")');
}
});
It is simple like maosmurf's example but still gives you the "lazy load" functionality of event firing when the element comes into view.
https://github.com/shrimpwagon/jquery-lazyloadanything

It's been a moment that this question is asked, but this doesn't mean that we can't share other answers in 2020. Here is an awesome plugin with jquery:jQuery Lazy
The basic usage of Lazy:
HTML
<!-- load background images of other element types -->
<div class="lazy" data-src="path/to/image.jpg"></div>
enter code here
JS
$('.lazy').Lazy({
// your configuration goes here
scrollDirection: 'vertical',
effect: 'fadeIn',
visibleOnly: true,
onError: function(element) {
console.log('error loading ' + element.data('src'));
}
});
and your background images are lazy loading. That's all!
To see real examples and more details check this link lazy-doc.

Without jQuery
HTML
background-image: url('default-loading-image');
data-src="image-you-want-to-load"
<div class="ajustedBackground" style="background-image: url('default-loading-image');" data-src="image-you-want-to-load"><div>
var tablinks = document.getElementsByClassName("ajustedBackground");
for (i = 0; i < tablinks.length; i++) {
var lazy = tablinks[i];
var src = lazy.dataset.src;
lazy.style.backgroundImage = 'url("'+src+'")';
}
.ajustedBackground{
width: 100%;
height: 300px;
background-size: 100%;
border-radius: 5px;
background-size: cover;
background-position: center;
position: relative;
}
<div class="ajustedBackground" style="background-image: url('https://monyo.az/resources/img/ezgif-6-b10ea37ef846.gif');" data-src="https://monyo.az/resources-qrcode/img/Fathir_7%201.png"><div>
Finds all ajustedBackground classname in html and load image from data-src
function lazyloadImages(){
var tablinks = document.getElementsByClassName("ajustedBackground");
for (i = 0; i < tablinks.length; i++) {
var lazy = tablinks[i];
var src = lazy.dataset.src;
lazy.style.background = 'url("'+src+'")';
}
}

Lazy loading images using above mentioned plugins uses conventional way of attaching listener to scroll events or by making use of setInterval and is highly non-performant as each call to getBoundingClientRect() forces the browser to re-layout the entire page and will introduce considerable jank to your website.
Use Lozad.js (just 569 bytes with no dependencies), which uses InteractionObserver to lazy load images performantly.

I had to deal with this for my responsive website. I have many different backgrounds for the same elements to deal with different screen widths. My solution is very simple, keep all your images scoped to a css selector, like "zoinked".
The logic:
If user scrolls, then load in styles with background images associated with them.
Done!
Here's what I wrote in a library I call "zoinked" I dunno why. It just happened ok?
(function(window, document, undefined) { var Z = function() {
this.hasScrolled = false;
if (window.addEventListener) {
window.addEventListener("scroll", this, false);
} else {
this.load();
} };
Z.prototype.handleEvent = function(e) {
if ($(window).scrollTop() > 2) {
this.hasScrolled = true;
window.removeEventListener("scroll", this);
this.load();
} };
Z.prototype.load = function() {
$(document.body).addClass("zoinked"); };
window.Zoink = Z;
})(window, document);
For the CSS I'll have all my styles like this:
.zoinked #graphic {background-image: url(large.jpg);}
#media(max-width: 480px) {.zoinked #graphic {background-image: url(small.jpg);}}
My technique with this is to load all the images after the top ones as soon as the user starts to scroll. If you wanted more control you could make the "zoinking" more intelligent.

Using jQuery I could load image with the check on it's existence. Added src to a plane base64 hash string with original image height width and then replaced it with the required url.
$('[data-src]').each(function() {
var $image_place_holder_element = $(this);
var image_url = $(this).data('src');
$("<div class='hidden-class' />").load(image_url, function(response, status, xhr) {
if (!(status == "error")) {
$image_place_holder_element.removeClass('image-placeholder');
$image_place_holder_element.attr('src', image_url);
}
}).remove();
});
Of course I used and modified few stack answers. Hope it helps someone.

This is an AngularJS Directive that will do this. Hope it helps someone
Usage:
<div background-image="{{thumbnailUrl}}"></div>
Code:
import * as angular from "angular";
export class BackgroundImageDirective implements angular.IDirective {
restrict = 'A';
link(scope: angular.IScope, element: angular.IAugmentedJQuery, attrs: angular.IAttributes) {
var backgroundImage = attrs["backgroundImage"];
let observerOptions = {
root: null,
rootMargin: "0px",
threshold: []
};
var intersectionCallback: IntersectionObserverCallback = (entries, self) => {
entries.forEach((entry) => {
let box = entry.target as HTMLElement;
if (entry.isIntersecting && !box.style.backgroundImage) {
box.style.backgroundImage = `url(${backgroundImage})`;
self.disconnect();
}
});
}
var observer = new IntersectionObserver(intersectionCallback, observerOptions);
observer.observe(element[0]);
}
static factory(): angular.IDirectiveFactory {
return () => new BackgroundImageDirective();
}
}

<div class="lazy" data-bg="img/bmw_m1_hood.jpg" style="width: 765px; height: 574px;"></div>
var lazyLoadInstance = new LazyLoad({
load_delay: 100,
effect : "fadeIn"
});
using the vanilla lazyload
https://www.npmjs.com/package/vanilla-lazyload

I know it's not related to the image load but here what I did in one of the job interview test.
HTML
<div id="news-feed">Scroll to see News (Newest First)</div>
CSS
article {
margin-top: 500px;
opacity: 0;
border: 2px solid #864488;
padding: 5px 10px 10px 5px;
background-image: -webkit-gradient(
linear,
left top,
left bottom,
color-stop(0, #DCD3E8),
color-stop(1, #BCA3CC)
);
background-image: -o-linear-gradient(bottom, #DCD3E8 0%, #BCA3CC 100%);
background-image: -moz-linear-gradient(bottom, #DCD3E8 0%, #BCA3CC 100%);
background-image: -webkit-linear-gradient(bottom, #DCD3E8 0%, #BCA3CC 100%);
background-image: -ms-linear-gradient(bottom, #DCD3E8 0%, #BCA3CC 100%);
background-image: linear-gradient(to bottom, #DCD3E8 0%, #BCA3CC 100%);
color: gray;
font-family: arial;
}
article h4 {
font-family: "Times New Roman";
margin: 5px 1px;
}
.main-news {
border: 5px double gray;
padding: 15px;
}
JavaScript
var newsData,
SortData = '',
i = 1;
$.getJSON("http://www.stellarbiotechnologies.com/media/press-releases/json", function(data) {
newsData = data.news;
function SortByDate(x,y) {
return ((x.published == y.published) ? 0 : ((x.published < y.published) ? 1 : -1 ));
}
var sortedNewsData = newsData.sort(SortByDate);
$.each( sortedNewsData, function( key, val ) {
SortData += '<article id="article' + i + '"><h4>Published on: ' + val.published + '</h4><div class="main-news">' + val.title + '</div></article>';
i++;
});
$('#news-feed').append(SortData);
});
$(window).scroll(function() {
var $window = $(window),
wH = $window.height(),
wS = $window.scrollTop() + 1
for (var j=0; j<$('article').length;j++) {
var eT = $('#article' + j ).offset().top,
eH = $('#article' + j ).outerHeight();
if (wS > ((eT + eH) - (wH))) {
$('#article' + j ).animate({'opacity': '1'}, 500);
}
}
});
I am sorting the data by Date and then doing lazy load on window scroll function.
I hope it helps :)
Demo

Related

How to change opacity of element when scrolled to bottom within media query

I just want to ask. I want to make the product image thumbnail in shopify disappear when I scrolled down to bottom of the page, and I want a bit of transition with it.. I really can't figure out how to do this..
Here's my code..
https://jsfiddle.net/vsLdz4qb/1/
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 750px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
Thank you so much in advance!
The correct document method is document.getElementsByClassName and since it returns an array you need the first element of it so change this:
document.getElementByClass("product-single__thumbnails").style.transition = "0.65s";
document.getElementByClass("product-single__thumbnails").style.opacity = 0;
to:
document.getElementsByClassName("product-single__thumbnails")[0].style.transition = "0.65s";
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
You can read more about the method here
You should use getElementsByClassName in place of getElementByClass(This is not correct function)
and this will return an array like structure so you need to pass 0 index, if only one class available on page.
or you can try querySelector(".product-single__thumbnails");
and for transition, you can define that in your .product-single__thumbnails class like: transition: opacity .65s linear; - use here which property, you want to animate.
<!-- [product-image] this is for product image scroll down disappear -->
function myFunction(screenWidth) {
if (screenWidth.matches) { // If media query matches
window.onscroll = function(ev) {
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight) {
document.getElementsByClassName("product-single__thumbnails")[0].style.opacity = 0;
}
};
}
}
let screenWidth = window.matchMedia("(min-width: 350px)");
myFunction(screenWidth); // Call listener function at run time
screenWidth.addListener(myFunction)
body {
margin:0;
height: 1000px;
}
.product-single__thumbnails {
background-color: red;
color: white;
width: 50px;
height: 50px;
position: fixed;
transition: opacity .65s linear;
border-radius: 4px;
margin: 20px;
text-align: center;
}
<div class="product-single__thumbnails">
<p>red</p>
</div>

Change style header/nav with Intersection Observer (IO)

Fiddle latest
I started this question with the scroll event approach, but due to the suggestion of using IntersectionObserver which seems much better approach i'm trying to get it to work in that way.
What is the goal:
I would like to change the style (color+background-color) of the header depending on what current div/section is observed by looking for (i'm thinking of?) its class or data that will override the default header style (black on white).
Header styling:
font-color:
Depending on the content (div/section) the default header should be able to change the font-color into only two possible colors:
black
white
background-color:
Depending on the content the background-color could have unlimited colors or be transparent, so would be better to address that separate, these are the probably the most used background-colors:
white (default)
black
no color (transparent)
CSS:
header {
position: fixed;
width: 100%;
top: 0;
line-height: 32px;
padding: 0 15px;
z-index: 5;
color: black; /* default */
background-color: white; /* default */
}
Div/section example with default header no change on content:
<div class="grid-30-span g-100vh">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_default_header.jpg"
class="lazyload"
alt="">
</div>
Div/section example change header on content:
<div class="grid-30-span g-100vh" data-color="white" data-background="darkblue">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_darkblue.jpg"
class="lazyload"
alt="">
</div>
<div class="grid-30-span g-100vh" data-color="white" data-background="black">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_black.jpg"
class="lazyload"
alt="">
</div>
Intersection Observer approach:
var mq = window.matchMedia( "(min-width: 568px)" );
if (mq.matches) {
// Add for mobile reset
document.addEventListener("DOMContentLoaded", function(event) {
// Add document load callback for leaving script in head
const header = document.querySelector('header');
const sections = document.querySelectorAll('div');
const config = {
rootMargin: '0px',
threshold: [0.00, 0.95]
};
const observer = new IntersectionObserver(function (entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
if (entry.intersectionRatio > 0.95) {
header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
} else {
if (entry.target.getBoundingClientRect().top < 0 ) {
header.style.color = entry.target.dataset.color !== undefined ? entry.target.dataset.color : "black";
header.style.background = entry.target.dataset.background !== undefined ? entry.target.dataset.background : "white";
}
}
}
});
}, config);
sections.forEach(section => {
observer.observe(section);
});
});
}
Instead of listening to scroll event you should have a look at Intersection Observer (IO).
This was designed to solve problems like yours. And it is much more performant than listening to scroll events and then calculating the position yourself.
First, here is a codepen which shows a solution for your problem.
I am not the author of this codepen and I would maybe do some things a bit different but it definitely shows you the basic approach on how to solve your problem.
Things I would change: You can see in the example that if you scoll 99% to a new section, the heading changes even tough the new section is not fully visible.
Now with that out of the way, some explaining on how this works (note, I will not blindly copy-paste from codepen, I will also change const to let, but use whatever is more appropriate for your project.
First, you have to specify the options for IO:
let options = {
rootMargin: '-50px 0px -55%'
}
let observer = new IntersectionObserver(callback, options);
In the example the IO is executing the callback once an element is 50px away from getting into view. I can't recommend some better values from the top of my head but if I would have the time I would try to tweak these parameters to see if I could get better results.
In the codepen they define the callback function inline, I just wrote it that way to make it clearer on what's happening where.
Next step for IO is to define some elements to watch. In your case you should add some class to your divs, like <div class="section">
let entries = document.querySelectorAll('div.section');
entries.forEach(entry => {observer.observe(entry);})
Finally you have to define the callback function:
entries.forEach(entry => {
if (entry.isIntersecting) {
//specify what should happen if an element is coming into view, like defined in the options.
}
});
Edit: As I said this is just an example on how to get you started, it's NOT a finished solution for you to copy paste. In the example based on the ID of the section that get's visible the current element is getting highlighted. You have to change this part so that instead of setting the active class to, for example, third element you set the color and background-color depending on some attribute you set on the Element. I would recommend using data attributes for that.
Edit 2: Of course you can continue using just scroll events, the official Polyfill from W3C uses scroll events to emulate IO for older browsers.it's just that listening for scroll event and calculating position is not performant, especially if there are multiple elements. So if you care about user experience I really recommend using IO. Just wanted to add this answer to show what the modern solution for such a problem would be.
Edit 3: I took my time to create an example based on IO, this should get you started.
Basically I defined two thresholds: One for 20 and one for 90%. If the element is 90% in the viewport then it's save to assume it will cover the header. So I set the class for the header to the element that is 90% in view.
Second threshold is for 20%, here we have to check if the element comes from the top or from the bottom into view. If it's visible 20% from the top then it will overlap with the header.
Adjust these values and adapt the logic as you see.
const sections = document.querySelectorAll('div');
const config = {
rootMargin: '0px',
threshold: [.2, .9]
};
const observer = new IntersectionObserver(function (entries, self) {
entries.forEach(entry => {
if (entry.isIntersecting) {
var headerEl = document.querySelector('header');
if (entry.intersectionRatio > 0.9) {
//intersection ratio bigger than 90%
//-> set header according to target
headerEl.className=entry.target.dataset.header;
} else {
//-> check if element is coming from top or from bottom into view
if (entry.target.getBoundingClientRect().top < 0 ) {
headerEl.className=entry.target.dataset.header;
}
}
}
});
}, config);
sections.forEach(section => {
observer.observe(section);
});
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
.g-100vh {
height: 100vh
}
header {
min-height: 50px;
position: fixed;
background-color: green;
width: 100%;
}
header.white-menu {
color: white;
background-color: black;
}
header.black-menu {
color: black;
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
<p>Header Content </p>
</header>
<div class="grid-30-span g-100vh white-menu" style="background-color:darkblue;" data-header="white-menu">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_darkblue.jpg"
class="lazyload"
alt="<?php echo $title; ?>">
</div>
<div class="grid-30-span g-100vh black-menu" style="background-color:lightgrey;" data-header="black-menu">
<img
src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1.414 1'%3E%3C/svg%3E"
data-src="/images/example_lightgrey.jpg"
class="lazyload"
alt="<?php echo $title; ?>">
</div>
I might not understand the question completely, but as for your example - you can solve it by using the mix-blend-mode css property without using javascript at all.
Example:
header {background: white; position: relative; height: 20vh;}
header h1 {
position: fixed;
color: white;
mix-blend-mode: difference;
}
div {height: 100vh; }
<header>
<h1>StudioX, Project Title, Category...</h1>
</header>
<div style="background-color:darkblue;"></div>
<div style="background-color:lightgrey;"></div>
I've encountered the same situation and the solution I implemented is very precise because it doesn't rely on percentages but on real elements' bounding boxes:
class Header {
constructor() {
this.header = document.querySelector("header");
this.span = this.header.querySelector('span');
this.invertedSections = document.querySelectorAll(".invertedSection");
window.addEventListener('resize', () => this.resetObserver());
this.resetObserver();
}
resetObserver() {
if (this.observer) this.observer.disconnect();
const {
top,
height
} = this.span.getBoundingClientRect();
this.observer = new IntersectionObserver(entries => this.observerCallback(entries), {
root: document,
rootMargin: `-${top}px 0px -${window.innerHeight - top - height}px 0px`,
});
this.invertedSections.forEach((el) => this.observer.observe(el));
};
observerCallback(entries) {
let inverted = false;
entries.forEach((entry) => {
if (entry.isIntersecting) inverted = true;
});
if (inverted) this.header.classList.add('inverted');
else this.header.classList.remove('inverted');
};
}
new Header();
header {
position: fixed;
top: 0;
left: 0;
right: 0;
padding: 20px 0;
text-transform: uppercase;
text-align: center;
font-weight: 700;
}
header.inverted {
color: #fff;
}
section {
height: 500px;
}
section.invertedSection {
background-color: #000;
}
<body>
<header>
<span>header</span>
</header>
<main>
<section></section>
<section class="invertedSection"></section>
<section></section>
<section class="invertedSection"></section>
</main>
</body>
What it does is actually quite simple: we can't use IntersectionObserver to know when the header and other elements are crossing (because the root must be a parent of the observed elements), but we can calculate the position and size of the header to add rootMargin to the observer.
Sometimes, the header is taller than its content (because of padding and other stuff) so I calculate the bounding-box of the span in the header (I want it to become white only when this element overlaps a black section).
Because the height of the window can change, I have to reset the IntersectionObserver on window resize.
The root property is set to document here because of iframe restrictions of the snippet (otherwise you can leave this field undefined).
With the rootMargin, I specify in which area I want the observer to look for intersections.
Then I observe every black section. In the callback function, I define if at least one section is overlapping, and if this is true, I add an inverted className to the header.
If we could use values like calc(100vh - 50px) in the rootMargin property, we may not need to use the resize listener.
We could even improve this system by adding side rootMargin, for instance if I have black sections that are only half of the window width and may or may not intersect with the span in the header depending on its horizontal position.
#Quentin D
I searched the internet for something like this, and I found this code to be the best solution for my needs.
Therefore I decided to build on it and create a universal "Observer" class, that can be used in many cases where IntesectionObserver is required, including changing the header styles.
I haven't tested it much, only in a few basic cases, and it worked for me. I haven't tested it on a page that has a horizontal scroll.
Having it this way makes it easy to use it, just save it as a .js file and include/import it in your code, something like a plugin. :)
I hope someone will find it useful.
If someone finds better ideas (especially for "horizontal" sites), it would be nice to see them here.
Edit: I hadn't made the correct "unobserve", so I fixed it.
/* The Intersection Observer API provides a way to asynchronously observe changes in the intersection of a target element with an ancestor element or with a top-level document's viewport.
ROOT:
It is not necessary for the root to be the ancestor element of the target. The root is allways the document, and the so-called root element is used only to get its size and position, to create an area in the document, with options.rootMargin.
Leave it false to have the viewport as root.
TARGET:
IntersectionObserver triggers when the target is entering at the specified ratio(s), and when it exits at the same ratio(s).
For more on IntersectionObserverEntry object, see:
https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API#targeting_an_element_to_be_observed
IntersectionObserverEntry.time // Timestamp when the change occurred
IntersectionObserverEntry.rootBounds // Unclipped area of root
IntersectionObserverEntry.intersectionRatio // Ratio of intersectionRect area to boundingClientRect area
IntersectionObserverEntry.target // the Element target
IntersectionObserverEntry.boundingClientRect // target.boundingClientRect()
IntersectionObserverEntry.intersectionRect // boundingClientRect, clipped by its containing block ancestors, and intersected with rootBounds
THRESHOLD:
Intersection ratio/threshold can be an array, and then it will trigger on each value, when in and when out.
If root element's size, for example, is only 10% of the target element's size, then intersection ratio/threshold can't be set to more than 10% (that is 0.1).
CALLBACKS:
There can be created two functions; when the target is entering and when it's exiting. These functions can do what's required for each event (visible/invisible).
Each function is passed three arguments, the root (html) element, IntersectionObserverEntry object, and intersectionObserver options used for that observer.
Set only root and targets to only have some info in the browser's console.
For more info on IntersectionObserver see: https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
Polyfill: <script src="https://polyfill.io/v3/polyfill.js?features=IntersectionObserver"></script>
or:
https://github.com/w3c/IntersectionObserver/tree/main/polyfill
Based on answer by Quentin D, answered Oct 27 '20 at 12:12
https://stackoverflow.com/questions/57834100/change-style-header-nav-with-intersection-observer-io
root - (any selector) - root element, intersection parent (only the first element is selected).
targets - (any selector) - observed elements that trigger function when visible/invisible.
inCb - (function name) - custom callback function to trigger when the target is intersecting.
outCb - (function name) - custom callback function to trigger when the target is not intersecting.
thres - (number 0-1) - threshold to trigger the observer (e.g. 0.1 will trigger when 10% is visible).
unobserve- (bolean) - if true, the target is unobserved after triggering the callback.
EXAMPLE:
(place in 'load' event listener, to have the correct dimensions)
var invertedHeader = new Observer({
root: '.header--main', // don't set to have the viewport as root
targets: '[data-bgd-dark]',
thres: [0, .16],
inCb: someCustomFunction,
});
*/
class Observer {
constructor({
root = false,
targets = false,
inCb = this.isIn,
outCb = this.isOut,
thres = 0,
unobserve = false,
} = {}) {
// this element's position creates with rootMargin the area in the document
// which is used as intersection observer's root area.
// the real root is allways the document.
this.area = document.querySelector(root); // intersection area
this.targets = document.querySelectorAll(targets); // intersection targets
this.inCallback = inCb; // callback when intersecting
this.outCallback = outCb; // callback when not intersecting
this.unobserve = unobserve; // unobserve after intersection
this.margins; // rootMargin for observer
this.windowW = document.documentElement.clientWidth;
this.windowH = document.documentElement.clientHeight;
// intersection is being checked like:
// if (entry.isIntersecting || entry.intersectionRatio >= this.ratio),
// and if ratio is 0, "entry.intersectionRatio >= this.ratio" will be true,
// even for non-intersecting elements, therefore:
this.ratio = thres;
if (Array.isArray(thres)) {
for (var i = 0; i < thres.length; i++) {
if (thres[i] == 0) {
this.ratio[i] = 0.0001;
}
}
} else {
if (thres == 0) {
this.ratio = 0.0001;
}
}
// if root selected use its position to create margins, else no margins (viewport as root)
if (this.area) {
this.iArea = this.area.getBoundingClientRect(); // intersection area
this.margins = `-${this.iArea.top}px -${(this.windowW - this.iArea.right)}px -${(this.windowH - this.iArea.bottom)}px -${this.iArea.left}px`;
} else {
this.margins = '0px';
}
// Keep this last (this.ratio has to be defined before).
// targets are required to create an observer.
if (this.targets) {
window.addEventListener('resize', () => this.resetObserver());
this.resetObserver();
}
}
resetObserver() {
if (this.observer) this.observer.disconnect();
const options = {
root: null, // null for the viewport
rootMargin: this.margins,
threshold: this.ratio,
}
this.observer = new IntersectionObserver(
entries => this.observerCallback(entries, options),
options,
);
this.targets.forEach((target) => this.observer.observe(target));
};
observerCallback(entries, options) {
entries.forEach(entry => {
// "entry.intersectionRatio >= this.ratio" for older browsers
if (entry.isIntersecting || entry.intersectionRatio >= this.ratio) {
// callback when visible
this.inCallback(this.area, entry, options);
// unobserve
if (this.unobserve) {
this.observer.unobserve(entry.target);
}
} else {
// callback when hidden
this.outCallback(this.area, entry, options);
// No unobserve, because all invisible targets will be unobserved automatically
}
});
};
isIn(rootElmnt, targetElmt, options) {
if (!rootElmnt) {
console.log(`IO Root: VIEWPORT`);
} else {
console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
}
console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS IN (${targetElmt.intersectionRatio * 100}%)`);
console.log(`IO Threshold: ${options.threshold}`);
//console.log(targetElmt.rootBounds);
console.log(`============================================`);
}
isOut(rootElmnt, targetElmt, options) {
if (!rootElmnt) {
console.log(`IO Root: VIEWPORT`);
} else {
console.log(`IO Root: ${rootElmnt.tagName} class="${rootElmnt.classList}"`);
}
console.log(`IO Target: ${targetElmt.target.tagName} class="${targetElmt.target.classList}" IS OUT `);
console.log(`============================================`);
}
}
This still needs adjustment, but you could try the following:
const header = document.getElementsByTagName('header')[0];
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
header.style.color = entry.target.dataset.color || '';
header.style.backgroundColor = entry.target.dataset.background;
}
});
}, { threshold: 0.51 });
[...document.getElementsByClassName('observed')].forEach((t) => {
t.dataset.background = t.dataset.background || window.getComputedStyle(t).backgroundColor;
observer.observe(t);
});
body {
font-family: arial;
margin: 0;
}
header {
border-bottom: 1px solid red;
margin: 0 auto;
width: 100vw;
display: flex;
justify-content: center;
position: fixed;
background: transparent;
transition: all 0.5s ease-out;
}
header div {
padding: 0.5rem 1rem;
border: 1px solid red;
margin: -1px -1px -1px 0;
}
.observed {
height: 100vh;
border: 1px solid black;
}
.observed:nth-of-type(2) {
background-color: grey;
}
.observed:nth-of-type(3) {
background-color: white;
}
<header>
<div>One</div>
<div>Two</div>
<div>Three</div>
</header>
<div class="observed">
<img src="http://placekitten.com/g/200/300">
<img src="http://placekitten.com/g/400/300">
</div>
<div class="observed" data-color="white" data-background="black">
<img src="http://placekitten.com/g/600/300">
</div>
<div class="observed" data-color="black" data-background="white">
<img src="http://placekitten.com/g/600/250">
</div>
The CSS ensures each observed section takes up 100vw and the observer does its thing when anyone of those comes into view by at least 51% percent.
In the callback the headers background-color is then set to the background-color of the intersecting element.

JS and CSS changing image on interval

i am wanting to have my webpage display a different background every minute or there abouts, the image is also quite large so it would have to fit using cover. The background is a background-image on the body here is my css code
#body {
background-image: url("home.jpg");
background-position: top;
background-size: cover;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
margin: 0px;
}
and i want to have the script change the url (or source img) to a differrent one every minute. I tryed this with setInteval and onLoad in html but i cant crack it!
Thanks very much
An easiest solution is to use jQuery backstretch plugin.
$.backstretch([
"http://dl.dropbox.com/u/515046/www/outside.jpg",
"http://dl.dropbox.com/u/515046/www/garfield-interior.jpg",
"http://dl.dropbox.com/u/515046/www/cheers.jpg"
], {duration: 3000, fade: 750});
Here is a jQuery only solution.
$(function() {
var body = $('body');
var backgrounds = new Array(
'url(https://dl.dropbox.com/u/515046/www/outside.jpg)',
'url(https://dl.dropbox.com/u/515046/www/garfield-interior.jpg)'
);
var current = 0;
function nextBackground() {
body.css(
'background',
backgrounds[current = ++current % backgrounds.length]
);
setTimeout(nextBackground, 10000);
}
setTimeout(nextBackground, 10000);
body.css('background', backgrounds[0]);
});
Reference
IMHO this code snippet will get you started.
// get reference to body
var main = document.body;
// next two lines of code allow toggle between colors to demonstrate this example.
var color = function() {
var col = "red";
return function() {
col = (col === "red" ? "blue" : "red");
return col;
};
};
var nextColor = color();
// using setTimeout instead of setInterval
function loop() {
var timeoutId = setTimeout(function() {
console.log('clearing', timeoutId)
clearTimeout(timeoutId);
main.style.background = nextColor();
loop();
}, 1000);
}
https://jsfiddle.net/4L4bww5r/

Potential function overloading in javascript: naming issue

I am new to Javascript but I was able to piece together something to create a random background image on page load. This was successfully used for a Div object on the page.
Since this worked well, I wanted to use this command again for a second Div object on the same page. Both Divs had separate CSS style names so I thought this would be fine. However as soon as I use both commands together, only one will work.
I assumed it was an overloading problem, but I tried renaming everything I could and it still hasn't solved it. Is there something else I need to rename that I'm missing or do I need to frame the two separate commands differently?
Below is the JS code, CSS and HTML:
Thanks in advance!
/*! random background image 2*/
window.onload = function frontimage() {
var thediv2 = document.getElementById("topimg");
var imgarray2 = new Array("f1.svg", "f2.svg");
var spot2 = Math.floor(Math.random()* imgarray2.length);
thediv2.style.background = "url(img/f-img/"+imgarray2[spot2]+")";
thediv2.style.backgroundSize = "70%";
thediv2.style.backgroundAttachment = "fixed";
thediv2.style.backgroundRepeat = "no-repeat";
thediv2.style.zIndex = "2";
thediv2.style.backgroundColor = "rgba(255,204,255,0.5)";
}
/*! random background image 1*/
window.onload = function backimage() {
var thediv = document.getElementById("imgmain");
var imgarray = new Array("b1.jpg", "b2.jpg", "b3.jpg", "b4.jpg", "b5.jpg");
var spot = Math.floor(Math.random()* imgarray.length);
thediv.style.background = "url(img/b-img/"+imgarray[spot]+")";
thediv.style.backgroundSize = "100%";
thediv.style.backgroundAttachment = "fixed";
thediv.style.backgroundRepeat = "no-repeat";
thediv.style.zIndex = "1";
}
#bigimg {
clear: both;
float: left;
margin-left: 0;
width: 100%;
display: block;
}
#imgmain {
background: 50% 0 no-repeat fixed;
z-index: 1;
width: 100%;
}
#topimg {
z-index: 2;
position: absolute;
background-image: url(../img/f-img/f2.svg);
background-repeat: no-repeat;
background-position: 50% -25%;
background-size:contain;
width: 100%;
margin: auto;
padding: 0;
}
<div id="bigimg">
<section id="imgmain"></section>
<section id="topimg"></section>
</div>
With addEventListener, you can add as many event handlers as you want.
window.addEventListener('load', function frontimage() {
// ...
});
window.addEventListener('load', function backimage() {
// ...
});
You are overriding your first window.onload by reassigning the callback function.
Try this:
window.onload = function() {
frontimage();
backimage();
}

Fading Banner Using `background:url()`

I have a banner enclosed in a div tag that contains my banner. I would like to get the banner to fade to the next image but unsure how to achieve the fading effect. I have tried using jQuery fadeIn() but it failed.
The reason why I need to use the background: url() is because I want this banner image to resize pleasantly when the browser gets resized. I am not sure if this is the best way of approaching my problem.
EDIT - My current code does swap the images in the banner, but does not apply the fadeIn() effect. The console does not report any errors.
CSS:
header div#banner {
background: url(../image/banner/00.jpg) no-repeat center;
-webkit-background-size: cover;
-moz-background-size: cover;
-o-background-size: cover;
background-size: cover;
height: 300px;
}
JavaScript:
var bannerImages = new Array();
var bannerCounter = 0;
function run() {
loadBannerImages();
runBannerTimer();
}
function loadBannerImages() {
var filePath = "image/banner/";
bannerImages[0] = filePath + "00.jpg";
bannerImages[1] = filePath + "01.jpg";
bannerImages[2] = filePath + "02.jpg";
bannerImages[3] = filePath + "03.jpg";
bannerImages[4] = filePath + "04.jpg";
}
function runBannerTimer() {
var t=setTimeout("swapBannerImage()",2000);
}
function swapBannerImage() {
$('#banner').fadeIn(1000, function() {
$('#banner').css('background', 'url(' + bannerImages[bannerCounter] + ') no-repeat center');
});
bannerCounter++;
if (bannerCounter >= bannerImages.length) {
bannerCounter = 0;
}
runBannerTimer();
}
Your setTimeout isn't correct; try the following instead:
function runBannerTimer() {
var t=setTimeout(function(){
swapBannerImage()
},2000);
}
EDIT
Here is the updated Banner Swap function:
function swapBannerImage() {
$('#banner').fadeOut('slow', function(){
$('#banner').css('background', 'url(' + bannerImages[bannerCounter] + ') no-repeat center').fadeIn('slow');
});
bannerCounter++;
if (bannerCounter >= bannerImages.length) {
bannerCounter = 0;
}
runBannerTimer();
}
Updated Demo Here
You could use multiple divs -- one per image -- and fade them in/out. The divs could still use the css background like you want, you'll just need to absolutely position them, so that they appear one on top of another. However, to get absolutely positioned divs to resize with the parent div (ie to get the "pleasant" resizing effect), you have to set up the css like so:
header div#banner {
... /* your background stuff here */
position: absolute;
left: 0;
right: 0;
height: 300px;
}
Note that you'll assign both left and right, which would make it take up the entire width of the parent. And, make sure that the parent has position:relative.

Categories