I love the gem render_async
because it’s a simple tool to make your controller action
thinner.
I was recently considering moving some data into a partial, but realized it was only being used in a single location below the fold.
Behold.
This is an easy way to use render_async
, but only rendering the data if it comes into the
viewport.
I originally found this solution while optimizing an image-heavy blog that really didn’t need to load images until you scroll the page.
Inside your Rails View: use your standard rails_async
code but make sure to be aware of the
classes and event name.
<%= render_async(your_path(resource), container_class: 'lazy_async',
toggle: { selector: ".lazy_async", event: "load-me"}) do %>
<div class="text-center p-5">
<small>Loading stuff...</small>
</div>
<% end %>
Now notice the javascript uses both the class name and event name.
const lazyClassName = "lazy_async"
const selectorName = "." + lazyClassName
function elementInView(el) {
el.classList.remove(lazyClassName);
const event = new Event('load-me');
el.dispatchEvent(event)
}
document.addEventListener("DOMContentLoaded", function () {
const lazyLoadContainers = document.querySelectorAll(selectorName);
if ("IntersectionObserver" in window) {
const imageObserver = new IntersectionObserver(function (entries, observer) {
entries.forEach(function (entry) {
if (entry.isIntersecting) {
const target = entry.target;
elementInView(target);
imageObserver.unobserve(target);
}
});
});
lazyLoadContainers.forEach(function (target) {
imageObserver.observe(target);
});
} else {
let lazyloadThrottleTimeout = undefined;
function lazyload() {
if (lazyloadThrottleTimeout) {
clearTimeout(lazyloadThrottleTimeout);
}
lazyloadThrottleTimeout = setTimeout(function () {
const scrollTop = window.scrollY;
lazyLoadContainers.forEach(function (target) {
if (target.offsetTop < window.innerHeight + scrollTop) {
elementInView(target);
}
});
if (lazyLoadContainers.length == 0) {
document.removeEventListener("scroll", lazyload);
window.removeEventListener("resize", lazyload);
window.removeEventListener("orientationChange", lazyload);
}
}, 20);
}
document.addEventListener("scroll", lazyload);
window.addEventListener("resize", lazyload);
window.addEventListener("orientationChange", lazyload);
}
});
This code is backwards compatible with older browsers and uses setTimeout if the navigator does not have IntersectionObserver.
IntersectionObserver is the industry standard for browser viewport intersection.
Once the observer is intialized, it will trigger on elements with isIntersecting{boolean}
to
be used however you want.
I like to use a helper method render_async_onscreen
def render_async_onscreen(path, options = {}, &placeholder)
container_id = "lazy_async_#{SecureRandom.hex(4)}"
options.reverse_merge!({ container_id: container_id, container_class: 'lazy_async', toggle: { selector: ".lazy_async", event: "load-me" } })
render_async(path, options, &placeholder)
end