luyadev/luya

View on GitHub
core/lazyload/LazyLoad.php

Summary

Maintainability
A
0 mins
Test Coverage
A
91%
<?php

namespace luya\lazyload;

use luya\base\Widget;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use luya\web\View;

/**
 * Image Lazy Loader.
 *
 * ```php
 * <?= LazyLoad::widget(['src' => 'http://www.zephir.ch/img/zephir-logo.png']); ?>
 * ```
 *
 * In order to read more visit the [[concept-lazyload.md]] guide section.
 *
 * @author Basil Suter <basil@nadar.io>
 * @author Marc Stampfli <marc@zephir.ch>
 * @author Alex Schmid <schmid@netfant.ch>
 * @since 1.0.0
 */
class LazyLoad extends Widget
{
    const JS_ASSET_KEY = 'lazyload.js.register';

    const CSS_ASSET_KEY = 'lazyload.css.register';
    const CSS_ASSET_KEY_PLACEHOLDER = 'lazyload.placeholder.css.register';

    /**
     * @var string The path to the image you want to lazy load.
     */
    public $src;

    /**
     * @var string Path for the placeholder image that will be base64 encoded.
     * @since 1.0.14
     */
    public $placeholderSrc;

    /**
     * @var boolean Inline the placeholder source as base64 encoded string
     * @since 1.0.14
     */
    public $placeholderAsBase64 = false;

    /**
     * @var integer The width of the image, this information should be provided in order to display a placeholder.
     */
    public $width;

    /**
     * @var integer The height of the image, this information should be provided in order to display a placeholder.
     */
    public $height;

    /**
     * @var boolean Define whether a full image tag should be return or only the attributes. This can be applied when using the lazy loader in background images.
     */
    public $attributesOnly = false;

    /**
     * @var string Additional classes for the lazy load image.
     */
    public $extraClass;

    /**
     * @var array Options array for the html tag. This array can be used to pass e.g. a `title` or `alt` tag.
     * @since 1.6.0
     */
    public $options = [];

    /**
     * @var boolean Legacy support for older Browsers (Adds the IntersectionOberserver Polyfill, default: true)
     * @since 1.6.0
     */
    public $legacySupport = true;

    /**
     * @var boolean Optionally disable the automatic init of the lazyload function so you can override the JS options
     * @since 1.6.0
     */
    public $initJs = true;

    /**
     * @var boolean If set to false, the size will be set by the placeholder (based on width/height). This enables
     * smoother fading of the image. Leave on true to have it work with CSS Frameworks like Bootstrap.
     * Has no effect if `attributesOnly` is `true`.
     * @since 1.6.1
     */
    public $replacePlaceholder = true;

    /**
     * @var string The default classes which will be registered.
     * @since 1.6.1
     */
    public $defaultCss = '
        .lazyimage-wrapper {
            display: block;
            width: 100%;
            position: relative;
            overflow: hidden;
        }
        .lazyimage {
            position: absolute;
            top: 50%;
            left: 50%;
            bottom: 0;
            right: 0;
            opacity: 0;
            height: 100%;
            width: 100%;
            -webkit-transition: .5s ease-in-out opacity;
            transition: .5s ease-in-out opacity;
            -webkit-transform: translate(-50%,-50%);
            transform: translate(-50%,-50%);
            -o-object-fit: cover;
            object-fit: cover;
            -o-object-position: center center;
            object-position: center center;
            z-index: 20;
        }
        .lazyimage.loaded {
            opacity: 1;
        }
        .lazyimage-placeholder {
            display: block;
            width: 100%;
            height: auto;
        }
        .nojs .lazyimage,
        .nojs .lazyimage-placeholder,
        .no-js .lazyimage,
        .no-js .lazyimage-placeholder {
            display: none;
        }
    ';

    /**
     * @inheritdoc
     */
    public function init()
    {
        parent::init();

        if ($this->src === null) {
            throw new InvalidConfigException("The parameter src is required by the lazyload widget.");
        }

        // register the asset file
        if ($this->legacySupport) {
            IntersectionObserverPolyfillAsset::register($this->view);
            $this->view->registerJs("IntersectionObserver.prototype.POLL_INTERVAL = 100;", View::POS_READY);
        }
        LazyLoadAsset::register($this->view);

        if ($this->initJs) {
            // register js and css code with keys in order to ensure the registration is done only once
            $this->view->registerJs("
                $.lazyLoad();
            ", View::POS_READY, self::JS_ASSET_KEY);
        }

        $this->view->registerCss($this->defaultCss, [], self::CSS_ASSET_KEY);
    }
    
    /**
     * Returns the aspect ration based on height or width.
     *
     * If no width or height is provided, the default value 0 will be returned.
     *
     * @return float A dot seperated ratio value
     * @since 1.6.1
     */
    protected function generateAspectRation()
    {
        return ($this->height && $this->width) ? str_replace(',', '.', ($this->height / $this->width) * 100) : 56.25;
    }

    /**
     * @inheritdoc
     */
    public function run()
    {
        if ($this->placeholderSrc && $this->placeholderAsBase64) {
            $this->placeholderSrc = 'data:image/jpg;base64,' . base64_encode(file_get_contents($this->placeholderSrc));
        }

        if ($this->attributesOnly && !$this->placeholderSrc) {
            return "class=\"js-lazyimage $this->extraClass\" data-src=\"$this->src\" data-width=\"$this->width\" data-height=\"$this->height\" data-as-background=\"1\"";
        }

        $tag = '<div class="lazyimage-wrapper ' . $this->extraClass . '">';
        $tag .= Html::tag('img', '', array_merge(
            $this->options,
            [
                'class' => 'js-lazyimage lazyimage' . ($this->replacePlaceholder ? (' ' . $this->extraClass) : ''),
                'data-src' => $this->src,
                'data-width' => $this->width,
                'data-height' => $this->height,
                'data-replace-placeholder' => $this->replacePlaceholder ? '1' : '0'
            ]
        ));
        if ($this->placeholderSrc) {
            $tag .= Html::tag('img', '', ['class' => 'lazyimage-placeholder', 'src' => $this->placeholderSrc]);
        } else {
            $tag .= '<div class="lazyimage-placeholder"><div style="display: block; height: 0px; padding-bottom: ' . $this->generateAspectRation() . '%;"></div><div class="loader"></div></div>';
        }
        $tag .= '<noscript><img class="loaded ' . $this->extraClass . '" src="'.$this->src.'" /></noscript>';
        $tag .= '</div>';

        return $tag;
    }
}