Accelerating Web Performance with PHP and JavaScript
Tags: webdev, php, performance, javascript
Published: 2024-03-25 14:11:01

## Table of Content
- [Introduction](#introduction)
- [Understanding Lazy-Loading](#understanding-lazy-loading)
  * [What is Lazy-Loading?](#what-is-lazy-loading-)
  * [Benefits of Lazy-Loading Images](#benefits-of-lazy-loading-images)
- [Prerequisites](#prerequisites)
  * [Basic Knowledge Required](#basic-knowledge-required)
  * [Tools and Environment Setup](#tools-and-environment-setup)
- [Analysing the Problem](#analysing-the-problem)
  * [Identifying the Bottleneck in Web Performance](#identifying-the-bottleneck-in-web-performance)
  * [Planning the Lazy-Loading Implementation](#planning-the-lazy-loading-implementation)
- [Lazy-Loading Implementation](#lazy-loading-implementation)
  * [PHP Script for Lazy-Loading](#php-script-for-lazy-loading)
  * [JavaScript for Lazy-Loading](#javascript-for-lazy-loading)
  * [Integrating the Solution](#integrating-the-solution)
  * [Usage Example:](#usage-example-)
- [Optimisation and Best Practices](#optimisation-and-best-practices)
  * [Additional Considerations](#additional-considerations)
  * [Monitoring and Continuous Improvement](#monitoring-and-continuous-improvement)
- [Results and Performance Gains](#results-and-performance-gains)
  * [Before and After Comparison](#before-and-after-comparison)
  * [Technical Insights](#technical-insights)
- [Summary and Conclusion](#summary-and-conclusion)
  * [Key Takeaways](#key-takeaways)
  * [Future Considerations](#future-considerations)


## Introduction
Managing the [Ekiti Parapo UK website](https://ekitiparapouk.org/), a platform built with PHP without the crutch of any framework, presented a unique set of challenges and learning opportunities. As a charity organisation committed to fostering community and cultural connections, the need for a robust online presence was undeniable. 

However, over time, the performance of the website began to decline, turning what should have been a smooth browsing experience into a test of patience for visitors.
![Internet Speed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/exb3z63vlknqjwhaq1dk.png)
Loading time was stretching to nearly two minutes for the homepage, you can literally go and boil water for tea while waiting for it to load, even on a decent 19Mbps internet connection, it was clear that a change was necessary.

![Chrome Developer Tools revealing an unexpected protagonist in this tale of dwindling performance: the images](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5e2l0a0z4etjl77sfnwy.png)
A deep dive into the Chrome Developer Tools revealed an unexpected protagonist in this tale of dwindling performance: the images. High-quality, impactful images had always been central to the digital content on the platform, but they had also become the Achilles' heel of the site's speed. 

The realisation brought back memories of frontend frameworks like [NuxtJS](https://nuxt.com/), where lazy-loading images—a technique to delay loading off-screen images until needed—was a breeze. Missing these features in the current setup of the codebase, I was inspired to embark on a mission: to manually implement lazy-loading and reclaim the website's lost efficiency instead of starting afresh with [NuxtJS](https://nuxt.com/).

This article chronicles my journey from identifying the problem to implementing a solution. Through a detailed exploration of lazy-loading with PHP and JavaScript, I hope to shed light on a path that can lead to significant performance improvements for any website facing similar issues.

## Understanding Lazy-Loading
One innovative strategy stands out in the quest for optimal website performance is **lazy-loading**. This technique, at its core, involves deferring the loading of non-critical resources at page load time, instead waiting until they are actually needed. While it can be applied to various types of content, lazy-loading images is particularly impactful due to the significant bandwidth images typically consume.

### What is Lazy-Loading?
Lazy-loading is a design pattern used to delay the initialisation of an object until the point at which it is needed. When applied to images on a website, it means that images outside of the viewport (the part of the webpage visible to the user) are not loaded until the user scrolls them into view. This approach can drastically reduce the initial load time of a page, improve user experience, and conserve data usage for users on limited data plans.

### Benefits of Lazy-Loading Images

**1. Improved Page Load Time:** By loading only the images that are immediately visible, the amount of data the browser needs to fetch on the initial page load is reduced, leading to faster load times.
**2.  Reduced Server Load:** Lazy-loading reduces the number of requests made to the server at one time, easing the load on the server and potentially saving bandwidth.
**3. Enhanced User Experience:** Users can start interacting with the content more quickly, which can improve engagement and reduce bounce rates.
**4. SEO Advantages:** Faster page load times are a factor in search engine rankings. By improving the speed of your site, you may also improve your site's visibility.

## Prerequisites
Before diving into the implementation of lazy-loading images using PHP and JavaScript, it's important to ensure you have a solid foundation in the necessary technologies and concepts. This section outlines the prerequisites that will enable you to successfully apply the techniques discussed in this guide.

### Basic Knowledge Required

**1. PHP:** A server-side scripting language used for web development. Familiarity with PHP syntax and basic programming concepts such as functions, loops, and arrays is essential.
**2. JavaScript (JS):** A client-side scripting language that enables dynamic content and interaction in web browsers. Understanding of DOM manipulation, event handling, and asynchronous programming (promises or async/await) is crucial.
**3. HTML:** The standard markup language for creating web pages. Knowledge of how to structure a webpage with HTML, including the use of `<img />` tags, is necessary.
**4. CSS (optional):** Though not directly related to lazy-loading, basic knowledge of CSS can be helpful for styling placeholders and loading animations.

### Tools and Environment Setup
**1. Text Editor or IDE:** A code editor such as Visual Studio Code, Sublime Text, or PHPStorm where you can write and edit your PHP and JavaScript code.
**2. Local Development Server:** A local server environment like XAMPP, WAMP, MAMP, or Docker to test your PHP scripts. PHP's built-in development server can also be used for simple testing.
**3. Web Browser with Developer Tools:** A modern web browser such as Google Chrome, Firefox, or Safari with developer tools for testing and debugging your code. Familiarity with the network tab and console in the browser's developer tools is beneficial.

## Analysing the Problem

![Network tab of the Chrome Developer Tools](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqv9oxem5e82p3lgr8ia.png)
The journey toward implementing lazy-loading began with a critical examination of the website's performance issues. Utilising the Chrome Developer Tools, a key discovery was made that would pave the way for significant improvements.

### Identifying the Bottleneck in Web Performance
The exploration started in the "Network" tab of the Chrome Developer Tools, focusing on the "Time" column to identify which requests were taking the longest to complete. This methodical approach led to an important realisation: the images were the main culprits behind the excessive load times.

The insight gained from sorting requests by their completion time highlighted the images' significant impact on the website's performance. This was a pivotal moment, underscoring the need for a solution that could address this specific challenge effectively.

### Planning the Lazy-Loading Implementation
Armed with this knowledge, the plan to implement lazy-loading took shape with a clear focus:

**1. Selection of Images:** I decided to apply lazy-loading to all images that were not immediately visible upon page load, which happens to be all the images below the slider. This strategy aimed to prioritise the loading of content that the user would first interact with, thereby enhancing the overall user experience.

![72kb Placeholder](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ne4ji6tug33lxxxv9nfw.png)
**2. Placeholder Image Strategy:** A critical aspect of the implementation was the introduction of a placeholder image. A low-size (72kb) placeholder, sourced from [loading.io](https://loading.io/) as gif image, was selected for its efficiency and its role in maintaining a smooth user experience while the actual images were being loaded.

**3. Utilisation of the Intersection Observer API:** The [Intersection Observer API](https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API) emerged as a central component of the lazy-loading strategy. Its ability to detect when an image enters the viewport without excessive resource consumption made it an ideal choice for triggering image loading precisely when needed.

**4. Asynchronous Image Decoding:** Recognising the potential for large images to freeze the browser during decoding, the strategy included the use of `img.decode()` to manage this process asynchronously. This approach ensured that the user interface remained responsive, even as high-quality images were being prepared for display.

## Lazy-Loading Implementation
The implementation process is divided into two main parts: creating a PHP function to dynamically render lazy-loading-ready image tags and writing JavaScript to handle the lazy-loading logic.

### PHP Script for Lazy-Loading
The first step is to create a PHP function that prepares your images for lazy-loading. This function will generate an `<img />` tag with a `data-src` attribute holding the actual image URL and a placeholder image as the default `src`.

**Creating the PHP Function:**
```
<?php
function lazyLoad($path, $attributes = [])
{
    $classValue = isset($attributes['class']) ? $attributes['class'] . ' php-lazy-loading' : 'php-lazy-loading';
    $attributes['class'] = $classValue;
    $imgTag = '<img data-src="' . htmlspecialchars($path) . '" src="images/pre-load.gif"';
    foreach ($attributes as $key => $value) {
        $imgTag .= ' ' . htmlspecialchars($key) . '="' . htmlspecialchars($value) . '"';
    }
    $imgTag .= '>';
    return $imgTag;
}
?>
```

- This function takes two parameters: the image path and an optional array of additional HTML attributes for the `<img />` tag.
- The class attribute is modified to include `'php-lazy-loading'` class, which is used by the JavaScript to identify images that should be lazy-loaded.
- A placeholder image (`images/pre-load.gif`) is set as the default image source.
- The actual image path is stored in the `data-src` attribute to be loaded later by JavaScript.

### JavaScript for Lazy-Loading
With the PHP function ready, the next step involves JavaScript to detect when the image should be loaded.

**Implementing Intersection Observer:**
```
<script>
document.addEventListener("DOMContentLoaded", function() {
    const lazyloadImages = document.querySelectorAll("img.php-lazy-loading");

    const imageObserver = new IntersectionObserver(function(entries, observer) {
        entries.forEach(function(entry) {
            if (entry.isIntersecting) {
                const image = entry.target;
                const img = new Image();
                img.src = image.dataset.src;
                img.decode().then(() => {
                    image.src = img.src;
                    image.onload = () => {
                        image.classList.remove("php-lazy-loading");
                    };
                    observer.unobserve(image);
                }).catch((error) => {
                    console.error("Image decoding failed", error);
                });
            }
        });
    }, {
        rootMargin: "0px 0px 200px 0px"
    });

    lazyloadImages.forEach(function(image) {
        imageObserver.observe(image);
    });
});
</script>
```
- This script waits for the DOM to fully load before querying for images marked with the `'php-lazy-loading'` class.
- The `IntersectionObserver` is used to monitor these images and load them as they approach the viewport, specified by `rootMargin`.
- Upon intersecting, the actual image is loaded asynchronously using the `Image.decode()` method, which helps prevent rendering [jank](https://www.afasterweb.com/2015/08/29/what-the-jank/).
- Once loaded, the placeholder is replaced with the actual image, and the image is removed from the observer to stop watching it.

### Integrating the Solution
To integrate this lazy-loading solution into your website, follow these steps:

**1. Incorporate the code:** Include the PHP file with the PHP function and JavaScript code on the page where images are rendered.

**2. Apply Lazy-Loading to Images:** Replace standard `<img />` tags with calls to the `lazyLoad()` function, passing the appropriate image path and attributes.

### Usage Example:
Replace:
```
<img
  class="img-fullwidth" 
  alt="260x230" 
  src='<?php echo "/admin/main/" . $row["picture"]; ?>'
  />
```
With:
```
<?php
include_once('util/load-image.php');

echo lazyLoad("admin/main/".$row['picture'], [
    'alt' => '260x230',
    'class' => 'img-fullwidth'
]);
?>
```

This example demonstrates how to use the `lazyLoad()` function within a PHP page, dynamically rendering an image with additional attributes like alt and class.


## Optimisation and Best Practices
Implementing lazy-loading is a significant step towards improving your website's performance, but to maximise its benefits, consider the following optimisations and best practices:
**1. Choosing Placeholder Images**
  - Size and Format: Ensure that your placeholder images are small in file size to speed up initial page load times. Formats like SVG or highly compressed JPEGs can be good choices.
  - Visual Representation: Use placeholders that hint at the content of the lazy-loaded image. A blurred version of the original image or a generic loading icon can maintain layout stability and improve the user experience.

**2. Setting Root Margins for the Intersection Observer**
  - Root Margin Explained: The `rootMargin` property in the Intersection Observer API allows you to specify how far from the viewport you'd like to begin loading your images. This is akin to a buffer zone around the viewport.
  - Adjusting Root Margin: A positive value (e.g., `"200px 0px"`) means the image will start loading before it enters the viewport, which can help ensure the image is loaded by the time the user sees it.

**3. Error Handling and Image Decoding Failures**
  - Graceful Fallbacks: Implement error handling for scenarios where an image fails to load or decode. This can include displaying a default error image or retrying the image load.
  - Asynchronous Decoding: The use of `img.decode()` in the JavaScript code ensures that decoding happens asynchronously, preventing the UI from freezing. Ensure that error handling is in place for browsers that do not support image decoding.

### Additional Considerations
- Lazy-Load Offscreen Iframes: Similar to images, iframes can also be lazy-loaded using the same Intersection Observer approach. This can further improve page load times, especially for pages with embedded videos or maps.
- Avoid Lazy-Loading Everything: Be judicious in what you choose to lazy-load. Critical images above the fold should be loaded normally to ensure they are immediately visible to the user.
- Test on Multiple Devices: Test your lazy-loading implementation across different devices and network conditions to ensure a consistent and smooth user experience.

### Monitoring and Continuous Improvement
- Performance Metrics: Use web performance monitoring tools to measure the impact of lazy-loading on your site.
- Feedback Loop: Gather feedback from real users about their experience with the lazy-loaded images. User feedback can provide insights that automated tools cannot.


## Results and Performance Gains
The implementation of lazy-loading images on the Ekiti Parapo UK website marks a significant milestone in the platform's evolution. By addressing the primary challenge of extended load times due to heavy image content, the initiative has yielded impressive results, enhancing both user experience and website performance.

### Before and After Comparison

![Initial Load Time](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0hs6a87vhh6ivhylvjd3.png)
- Initial Load Time: Prior to the implementation, the homepage took almost **_2 minutes (around 102 seconds)_** to fully load. This sluggish performance was a barrier to user engagement and negatively impacted the overall user experience.

![Post-Implementation Load Time](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/36p1hgf7j56znuandsj1.png)
- Post-Implementation Load Time: After integrating lazy-loading, the homepage load time dramatically reduced to approximately **_9 seconds_**. This represents a whooping **_91.18%_** reduction in load time, significantly surpassing initial expectations and setting a new benchmark for the website's performance.

**Impact on User Experience**
The improvement in load time has had a direct and positive impact on user experience. Visitors to the Ekiti Parapo UK website now enjoy a smoother, faster browsing experience, with images loading dynamically as they scroll through the content.

**SEO Improvements**
Faster page load times are a critical factor in search engine optimisation (SEO). By significantly reducing the load time of the homepage, the website stands a better chance of achieving higher rankings in search engine results pages (SERPs).

### Technical Insights
The transition to lazy-loading required a detailed technical implementation, as outlined in previous sections. The process illuminated several key insights:

**1. Efficiency of Intersection Observer API:** The use of the Intersection Observer API proved to be highly efficient and effective for detecting when images enter the viewport, minimising unnecessary image loads.
**2. Asynchronous Image Decoding:** Implementing asynchronous image decoding helped prevent browser freezes, ensuring a smooth user experience even as high-quality images were loaded.
**3. Placeholder Strategy:** The strategic use of placeholders maintained the layout integrity during image loading, enhancing the perceived performance for users.


## Summary and Conclusion
The journey of optimising the Ekiti Parapo UK website by implementing lazy-loading for images has been both challenging and rewarding. Beginning with a pressing need to address significantly prolonged load times, the initiative culminated in a remarkable improvement in performance, user experience, and potentially SEO rankings. The success of this project underscores several key points and lessons for the web development community.

### Key Takeaways
**1. Performance Optimisation is Crucial:** In the digital age, where user attention spans are short, and competition is fierce, optimising website performance is not just beneficial but essential. The dramatic reduction in load time from nearly **_2 minutes_** to just **_9 seconds_** illustrates the tangible benefits that can be achieved through focused optimisation efforts.

**2. Lazy-Loading as an Effective Strategy:** The implementation of lazy-loading has proven to be a highly effective strategy for improving website speed and efficiency. By loading images only as they are needed, websites can significantly reduce initial load times and resource consumption, enhancing the user experience.

**3. Technical Implementation is Accessible:** With the right approach and understanding, incorporating sophisticated performance optimisations like lazy-loading is achievable even outside of modern development frameworks. The use of PHP and JavaScript, as demonstrated, allows for significant improvements with manageable complexity.

**4. Continuous Improvement and Adaptation:** The web development landscape is ever-evolving, and so are the techniques and strategies for optimization.

### Future Considerations
Looking ahead, there is potential to further refine and expand the lazy-loading implementation. Exploring advanced techniques, such as adaptive image loading based on network conditions, could provide additional performance benefits and user experience enhancements.