...
 
Commits (20)
......@@ -15,35 +15,114 @@ and **inline-iframes**.
You can still use this module tandem with it, though that is not a requirement.
## Requirements
* **[Libraries API][4]** module
* **bLazy v1.8.2** script as a library item:
[Download bLazy][3] from https://github.com/dinbror/blazy
1) Extract the downloaded file,
2) rename *blazy-master* directory to *blazy*,
3) copy the folder into one of the following places that *Libraries API*
module supports, `sites/all/libraries` (or site-specific libraries folder):
i.e.: `sites/all/libraries/blazy/blazy.min.js`
* **bLazy v1.8.2** script as a library item
## Installing Manually
- [Download bLazy][3] from https://github.com/dinbror/blazy
- Extract the downloaded file,
- rename *blazy-master* directory to *blazy*,
- copy the folder into the `/libraries` folder: i.e.: `libraries/blazy/blazy.min.js`
## Installing via Composer
- Run `composer require --prefer-dist composer/installers` to ensure that you have the `composer/installers` package installed. This package facilitates the installation of packages into directories other than `/vendor` (e.g. `/libraries`) using Composer.
- If your `composer.json` doesn't already have a definition for the `libraries` path, define one similar to the one below, depending on your setup:
``` json
"extra": {
"installer-paths": {
"web/libraries/{$name}": ["type:drupal-library"]
}
}
```
- Add following to the "repositories" section of `composer.json`:
``` json
"repositories": [
{
"type": "package",
"package": {
"name": "dinbror/blazy",
"version": "1.8.2",
"type": "drupal-library",
"extra": {
"installer-name": "blazy"
},
"source": {
"type": "git",
"url": "https://github.com/dinbror/blazy",
"reference": "1.8.2"
}
}
}
## Installation
]
```
- Install the required _Blazy_ library:
``` sh
composer require 'dinbror/blazy:1.8.2'
```
- Install this module:
``` sh
composer require 'drupal/lazy:^2.0'
```
Install the module as usual. More information can be found at
https://www.drupal.org/docs/7/extend/installing-modules
## Usage
There are two options to set up for your site to lazy-load images. And both options share the same [settings](/admin/config/content/lazy).
1. Image fields
2. Inline images and Iframes managed in rich-text fields (ckeditor)
### Image fields
Repeat these steps for each image field you want to enable lazy-loading:
1. Go to **Manage display** page of the entity type you want to enable lazy-loading. *i.e. Article*
2. Select the **display** you want the change. *i.e. Teaser*
3. Click on the little cog icon to edit image display settings
4. Check **Enable lazy-loading** option, update the field settings, and save.
### Inline images and Iframes
1. **Configure** the [text format](/admin/config/content/formats) you want to enable lazy-loading. *i.e. Full HTML*
2. Check **Lazy-load images and IFRAMEs via bLazy** option to enable the filter, and save configuration.
3. Go to [Lazy-load settings](/admin/config/content/lazy).
4. Check the boxes for the inline elements (`<img>`, `<iframe>`) to be lazy-loaded via filter.
5. Save configuration
*Repeat steps 1-2 for each text-format you want to enable lazy-loading.*
To disable lazy-loading for specific image or iframes, add **skip-class** to the
class attribute. Default value (no-b-lazy) can be changed in the configuration.
``` html
<img class="no-b-lazy" src="this-image-will-load-normal" alt="">
```
### Blazy plugin can be manipulated in your theme/module javascript:
This modules makes a new text filter available for the text-formats: *Lazy-load*
The options in **Blazy configuration** section of the settings form are the default options of the Blazy library. Refer to [Blazy plugin documentation][5] for each setting.
Enable the *Lazy-load* filter for the desired text-formats. i.e. *Full HTML* or
*Filtered HTML*
#### Get Blazy plugin options.
```js
let opt = drupalSettings.lazy;
```
Check out the module configuration at `admin/config/content/lazy`. The default
settings should work for most developers. Incase they are not, change the
settings to suit your needs and submit the form.
#### Access Blazy's public functions:
| Function | Description |
|:--|:--|
| `Drupal.lazy.revalidate();` | Revalidates document for visible images. Useful if you add images with scripting or ajax |
| `Drupal.lazy.load(element(s), force);` | Forces the given element(s) to load if not collapsed. If you also want to load a collapsed/hidden elements you can add true as the second parameter. You can pass a single element or a list of elements. Tested with getElementById, getElementsByClassName, querySelectorAll, querySelector and jQuery selector. |
| `Drupal.lazy.destroy();` | Unbind events and resets image array |
This configuration is used globally for all the text-formats having *Lazy-load*
filter enabled.
## Use Case
......@@ -65,3 +144,4 @@ cached, there should not be any changes in performance.
[2]: https://www.drupal.org/project/blazy
[3]: https://github.com/dinbror/blazy/archive/master.zip
[4]: https://www.drupal.org/project/libraries
[5]: http://dinbror.dk/blog/blazy/
{
"name": "drupal/lazy",
"type": "drupal-module",
"description": "Lazy-loading images and iframes",
"keywords": ["Drupal", "bLazy", "Lazy-load"],
"license": "GPL-2.0+",
"homepage": "https://www.drupal.org/project/lazy",
"authors": [
{
"name": "Osman Gormus",
"homepage": "https://www.drupal.org/u/osman",
"role": "Maintainer"
}
],
"support": {
"issues": "https://www.drupal.org/project/issues/lazy",
"source": "http://cgit.drupalcode.org/lazy"
}
}
errorClass: "b-error"
loadInvisible: false
offset: 100
saveViewportOffsetDelay: 50
selector: "b-lazy"
alter_tag:
img: 0
iframe: 0
skipClass: "no-b-lazy"
src: "data-src"
successClass: "b-loaded"
validateDelay: 25
placeholderSrc: "data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=="
image_fields: false
disabled_paths: "/rss.xml"
\ No newline at end of file
(function ($) {
(function () {
'use strict';
Drupal.behaviors.lazy = {
attach: function (context, settings) {
var options = settings.lazy.bLazy ? settings.lazy.bLazy : {};
new Blazy(options);
var options = settings.lazy ? settings.lazy : {};
Drupal.lazy = new Blazy(options);
}
};
})(jQuery);
})();
<?php
/**
* @file
* Configuration form for Lazy-load.
*/
/**
* Lazy-load configuration form.
*/
function lazy_configuration_form($form, $form_state) {
$defaults = variable_get('lazy_filter_defaults');
$filter_enabled = lazy_is_filter_enabled();
$description = t('The %filter filter must be enabled for at least one <a href="!path">text-format</a>.', array(
'!path' => url('admin/config/content/formats'),
'%filter' => 'Lazy-load',
));
$form['settings'] = array(
'#type' => 'fieldset',
'#title' => t('Global settings'),
'#collapsible' => FALSE,
);
$form['settings']['lazy_filter_alter_tag'] = array(
'#type' => 'checkboxes',
'#title' => t('Select the inline elements to be lazy-loaded via filter.'),
'#options' => array(
'img' => t('Images (%img tags)', array('%img' => '<img>')),
'iframe' => t('Iframes (%iframe tags)', array('%iframe' => '<iframe>')),
),
'#default_value' => variable_get('lazy_filter_alter_tag', $defaults['lazy_filter_alter_tag']),
'#description' => $filter_enabled ? '' : $description,
'#disabled' => !$filter_enabled,
);
$form['settings']['lazy_filter_image_fields'] = array(
'#type' => 'checkbox',
'#title' => t('Enable on image fields attached to fieldable entities. For example, content-types, blocks.'),
'#description' => t('Default is %val', array('%val' => ($defaults['lazy_filter_loadInvisible']) ? 'checked' : 'unchecked')),
'#default_value' => variable_get('lazy_filter_image_fields', $defaults['lazy_filter_image_fields']),
'#return_value' => TRUE,
);
$form['settings']['lazy_filter_placeholderSrc'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder image URL'),
'#description' => t('Default is %val', array('%val' => $defaults['lazy_filter_placeholderSrc'])),
'#default_value' => variable_get('lazy_filter_placeholderSrc', $defaults['lazy_filter_placeholderSrc']),
);
$form['blazy'] = array(
'#type' => 'fieldset',
'#title' => t('bLazy configuration'),
'#description' => t('<p><a href="!url">bLazy</a> is a lightweight lazy loading and multi-serving image script created by Bjoern Klinggaard. See its website for usage details and demos.</p>', array(
'!url' => 'http://dinbror.dk/blog/blazy/',
)),
'#collapsible' => TRUE,
'#collapsed' => TRUE,
);
$form['blazy']['lazy_filter_loadInvisible'] = array(
'#type' => 'checkbox',
'#title' => t('loadInvisible'),
'#description' => t('If checked loads invisible (hidden) elements. Default is %val', array('%val' => ($defaults['lazy_filter_loadInvisible']) ? 'checked' : 'unchecked')),
'#default_value' => variable_get('lazy_filter_loadInvisible', $defaults['lazy_filter_loadInvisible']),
'#return_value' => TRUE,
);
$form['blazy']['lazy_filter_offset'] = array(
'#type' => module_exists('elements') ? 'numberfield' : 'textfield',
'#title' => t('offset'),
'#description' => t('The offset controls how early you want the elements to be loaded before they’re visible. Default is %val, so %val pixel before an element is visible it’ll start loading.', array('%val' => $defaults['lazy_filter_offset'])),
'#default_value' => variable_get('lazy_filter_offset', $defaults['lazy_filter_offset']),
);
$form['blazy']['lazy_filter_saveViewportOffsetDelay'] = array(
'#type' => module_exists('elements') ? 'numberfield' : 'textfield',
'#title' => t('saveViewportOffsetDelay'),
'#description' => t('Delay for how often it should call the saveViewportOffset function on resize. Default is %val', array('%val' => $defaults['lazy_filter_saveViewportOffsetDelay'])),
'#default_value' => variable_get('lazy_filter_saveViewportOffsetDelay', $defaults['lazy_filter_saveViewportOffsetDelay']),
);
$form['blazy']['lazy_filter_validateDelay'] = array(
'#type' => module_exists('elements') ? 'numberfield' : 'textfield',
'#title' => t('validateDelay'),
'#description' => t('Delay for how often it should call the validate function on scroll/resize. Default is %val', array('%val' => $defaults['lazy_filter_validateDelay'])),
'#default_value' => variable_get('lazy_filter_validateDelay', $defaults['lazy_filter_validateDelay']),
);
$form['blazy']['lazy_filter_selector'] = array(
'#type' => 'textfield',
'#title' => t('Selector class'),
'#description' => t('Element selector for elements that should lazy load. Do not include a leading period. Default is %val', array('%val' => $defaults['lazy_filter_selector'])),
'#default_value' => variable_get('lazy_filter_selector', $defaults['lazy_filter_selector']),
);
$form['blazy']['lazy_filter_skipClass'] = array(
'#type' => 'textfield',
'#title' => t('skipClass'),
'#description' => t('Elements having this class name will be ignored. Default is %val', array('%val' => $defaults['lazy_filter_skipClass'])),
'#default_value' => variable_get('lazy_filter_skipClass', $defaults['lazy_filter_skipClass']),
);
$form['blazy']['lazy_filter_errorClass'] = array(
'#type' => 'textfield',
'#title' => t('errorClass'),
'#description' => t('The classname an element will get if something goes wrong. Default is %val', array('%val' => $defaults['lazy_filter_errorClass'])),
'#default_value' => variable_get('lazy_filter_errorClass', $defaults['lazy_filter_errorClass']),
);
$form['blazy']['lazy_filter_successClass'] = array(
'#type' => 'textfield',
'#title' => t('successClass'),
'#description' => t('The classname an element will get when loaded. Default is %val', array('%val' => $defaults['lazy_filter_successClass'])),
'#default_value' => variable_get('lazy_filter_successClass', $defaults['lazy_filter_successClass']),
);
$form['blazy']['lazy_filter_src'] = array(
'#type' => 'textfield',
'#title' => t('src'),
'#description' => t('Attribute where the original element source will be assigned. Do not change this unless attribute is used for other purposes. Default is %val', array('%val' => $defaults['lazy_filter_src'])),
'#default_value' => variable_get('lazy_filter_src', $defaults['lazy_filter_src']),
);
$form['submit'] = array(
'#type' => 'submit',
'#value' => t('Save configuration'),
);
return $form;
}
/**
* Submit handler for lazy_configuration_form().
*/
function lazy_configuration_form_submit($form, &$form_state) {
$int_value = array(
'lazy_filter_offset',
'lazy_filter_saveViewportOffsetDelay',
'lazy_filter_validateDelay',
);
$bool_value = array(
'lazy_filter_loadInvisible',
);
// Exclude unnecessary elements.
form_state_values_clean($form_state);
foreach ($form_state['values'] as $key => $value) {
if (in_array($key, $int_value)) {
$value = intval($value);
}
if (in_array($key, $bool_value)) {
$value = boolval($value);
}
variable_set($key, $value);
}
drupal_set_message(t('The configuration options have been saved.'));
}
name = Lazy-load
description = "This module integrates bLazy script to lazy load inline images and/or iframes via input-filters."
core = 7.x
dependencies[] = drupal:filter
dependencies[] = libraries
package = "Input filters"
configure = admin/config/content/lazy
name: 'Lazy-load'
type: module
description: 'This module integrates bLazy script to lazy load inline images and/or iframes via input-filters.'
core: 8.x
package: 'Input filters'
configure: lazy.config_form
dependencies:
- drupal:filter
......@@ -5,90 +5,33 @@
* Install, update, and uninstall functions for the Lazy-load module.
*/
/**
* Implements hook_install().
*/
function lazy_install() {
$defaults = array(
'lazy_filter_errorClass' => 'b-error',
'lazy_filter_loadInvisible' => FALSE,
'lazy_filter_offset' => 100,
'lazy_filter_saveViewportOffsetDelay' => 50,
'lazy_filter_selector' => 'b-lazy',
'lazy_filter_alter_tag' => array('img' => 'img', 'iframe' => 'iframe'),
'lazy_filter_skipClass' => 'no-b-lazy',
'lazy_filter_src' => 'data-src',
'lazy_filter_successClass' => 'b-loaded',
'lazy_filter_validateDelay' => 25,
'lazy_filter_placeholderSrc' => 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==',
'lazy_filter_image_fields' => FALSE,
);
variable_set('lazy_filter_defaults', $defaults);
foreach ($defaults as $key => $value) {
variable_set($key, $value);
}
}
/**
* Implements hook_enable().
*/
function lazy_enable() {
$options = array(
'!config' => url('admin/config/content/lazy'),
'%filter' => 'Lazy-load',
'!path' => url('admin/config/content/formats'),
);
$message = t('The <a href="!config">Lazy</a> module was installed. The new %filter filter should be enabled in desired <a href="!path">text-formats</a>.', $options);
drupal_set_message($message, 'warning');
}
/**
* Implements hook_uninstall().
*/
function lazy_uninstall() {
variable_del('lazy_filter_defaults');
variable_del('lazy_filter_errorClass');
variable_del('lazy_filter_loadInvisible');
variable_del('lazy_filter_offset');
variable_del('lazy_filter_saveViewportOffsetDelay');
variable_del('lazy_filter_selector');
variable_del('lazy_filter_alter_tag');
variable_del('lazy_filter_skipClass');
variable_del('lazy_filter_src');
variable_del('lazy_filter_successClass');
variable_del('lazy_filter_validateDelay');
variable_del('lazy_filter_placeholderSrc');
variable_del('lazy_filter_image_fields');
$module = 'lazy';
$name = 'lazy_filter';
db_delete('filter')
->condition('module', $module)
->condition('name', $name)
->execute();
}
/**
* Implements hook_requirements().
*/
function lazy_requirements($phase) {
$requirements = array();
$t = get_t();
$requirements = [];
if ($phase == 'runtime') {
$blazy = libraries_detect('blazy');
$requirements['blazy'] = array(
'title' => $t('bLazy'),
);
if ($blazy['installed']) {
$requirements['blazy']['value'] = $blazy['version'];
$requirements['blazy']['severity'] = REQUIREMENT_OK;
$library = Drupal::service('library.discovery')
->getLibraryByName('lazy', 'lazy-blazy');
$has_blazy = FALSE;
if ($library['js']) {
foreach ($library['js'] as $js) {
if (($js['type'] == 'file') && file_exists(DRUPAL_ROOT . '/' . $js['data'])) {
$has_blazy = TRUE;
}
}
}
else {
$requirements['blazy']['value'] = $blazy['error'];
$requirements['blazy']['description'] = $blazy['error message'];
$requirements['blazy']['severity'] = REQUIREMENT_ERROR;
$requirements['blazy'] = [
'title' => t('bLazy library'),
];
$requirements['blazy']['value'] = $has_blazy ? t('Enabled') : t('Not found');
$requirements['blazy']['severity'] = $has_blazy ? REQUIREMENT_OK : REQUIREMENT_WARNING;
if (!$has_blazy) {
$requirements['blazy']['description'] = t('Lazy-load modules requires the bLazy library. See README.md file for instructions.');
}
}
......@@ -96,34 +39,28 @@ function lazy_requirements($phase) {
}
/**
* Update module default values.
* Reset "disabled_paths" configuration to module default.
*/
function lazy_update_7100() {
// Get current default values.
$defaults = variable_get('lazy_filter_defaults');
function lazy_update_8201() {
$config = \Drupal::service('config.factory')->getEditable('lazy.settings');
// Update it from old string value to new array value.
$defaults['lazy_filter_alter_tag'] = array('img' => 'img', 'iframe' => 'iframe');
// Fix already existing value with new format.
$lazy_filter_alter_tag = variable_get('lazy_filter_alter_tag');
switch ($lazy_filter_alter_tag) {
case 'img':
variable_set('lazy_filter_alter_tag', array('img' => 'img', 'iframe' => 0));
break;
if ($config->get('disabled_paths') === NULL) {
$config->set('disabled_paths', '/rss.xml');
return t('The new "disabled_paths" configuration is set to "/rss.xml" (default value).');
}
case 'iframe':
variable_set('lazy_filter_alter_tag', array('img' => 0, 'iframe' => 'iframe'));
break;
return NULL;
}
default:
variable_set('lazy_filter_alter_tag', $defaults['lazy_filter_alter_tag']);
break;
/**
* Reset "image_fields" configuration to module default.
*/
function lazy_update_8202() {
$config = \Drupal::service('config.factory')->getEditable('lazy.settings');
if ($config->get('image_fields') === TRUE) {
$config->set('image_fields', FALSE);
return t('<b>Action needed:</b> As of 8.x-2.x, image fields are now controlled individually. You need to manually update each image field to enable lazy-loading.');
}
// Add new setting to support image fields.
$defaults['lazy_filter_image_fields'] = FALSE;
// Update the defaults with new values.
variable_set('lazy_filter_defaults', $defaults);
variable_set('lazy_filter_image_fields', $defaults['lazy_filter_image_fields']);
return NULL;
}
lazy:
version: VERSION
js:
js/lazy.js: { }
dependencies:
- core/drupal
- core/drupalSettings
- lazy/lazy-blazy
lazy-blazy:
header: true
remote: https://github.com/dinbror/blazy
version: 1.8.2
license:
name: MIT
url: https://github.com/dinbror/blazy/blob/master/LICENSE
gpl-compatible: true
js:
# //cdn.jsdelivr.net/blazy/latest/blazy.min.js: { type: external, minified: true }
/libraries/blazy/blazy.min.js: { minified: true }
lazy.config_form:
title: 'Lazy-load configuration'
parent: system.admin_config_content
description: 'Configure how images and iframes are lazy-loaded in content.'
route_name: lazy.config_form
......@@ -5,287 +5,251 @@
* Module file for Lazy-load.
*/
/**
* Implements hook_menu().
*/
function lazy_menu() {
return array(
'admin/config/content/lazy' => array(
'title' => 'Lazy-load',
'description' => 'Configure how images and iframes are lazy-loaded.',
'page callback' => 'drupal_get_form',
'page arguments' => array('lazy_configuration_form'),
'file' => 'lazy.admin.inc',
'access arguments' => array('administer filters'),
),
);
}
use Drupal\Core\Entity\Entity\EntityViewDisplay;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Routing\RouteMatchInterface;
/**
* Implements hook_help().
*/
function lazy_help($path, $arg) {
switch ($path) {
case 'admin/help#lazy':
$filepath = dirname(__FILE__) . '/README.md';
if (file_exists($filepath)) {
$readme = file_get_contents($filepath);
}
if (!isset($readme)) {
return NULL;
}
if (module_exists('markdown')) {
$filters = module_invoke('markdown', 'filter_info');
$info = $filters['filter_markdown'];
if (function_exists($info['process callback'])) {
$output = $info['process callback']($readme, NULL);
}
else {
$output = '<pre>' . $readme . '</pre>';
}
}
else {
$output = '<pre>' . $readme . '</pre>';
function lazy_help($route_name, RouteMatchInterface $route_match) {
switch ($route_name) {
case 'help.page.lazy':
$readme = file_get_contents(__DIR__ . '/README.md');
if (\Drupal::moduleHandler()->moduleExists('markdown')) {
// Use the Markdown filter to render the README.
$filter_manager = \Drupal::service('plugin.manager.filter');
$settings = \Drupal::configFactory()->get('markdown.settings')->getRawData();
$config = ['settings' => $settings];
$filter = $filter_manager->createInstance('markdown', $config);
return $filter->process($readme, 'en');
}
return $output;
return '<pre>' . $readme . '</pre>';
}
}
/**
* Implements hook_filter_info().
*/
function lazy_filter_info() {
$filters['lazy_filter'] = array(
'title' => t('Lazy-load'),
'description' => t('Lazy-load inline-images and/or iframes'),
'default settings' => _filter_lazy_defaults(),
'process callback' => '_filter_lazy_process',
'cache' => TRUE,
'tips callback' => '_filter_lazy_tips',
'weight' => 20,
);
return $filters;
return NULL;
}
/**
* Callback to return configuration defaults.
* Returns supported field-types.
*
* @return array
*/
function _filter_lazy_defaults() {
return array(
'lazy_filter_errorClass' => variable_get('lazy_filter_errorClass'),
'lazy_filter_loadInvisible' => variable_get('lazy_filter_loadInvisible'),
'lazy_filter_offset' => variable_get('lazy_filter_offset'),
'lazy_filter_saveViewportOffsetDelay' => variable_get('lazy_filter_saveViewportOffsetDelay'),
'lazy_filter_selector' => variable_get('lazy_filter_selector'),
'lazy_filter_alter_tag' => variable_get('lazy_filter_alter_tag'),
'lazy_filter_skipClass' => variable_get('lazy_filter_skipClass'),
'lazy_filter_src' => variable_get('lazy_filter_src'),
'lazy_filter_successClass' => variable_get('lazy_filter_successClass'),
'lazy_filter_validateDelay' => variable_get('lazy_filter_validateDelay'),
'lazy_filter_placeholderSrc' => variable_get('lazy_filter_placeholderSrc'),
'lazy_filter_image_fields' => variable_get('lazy_filter_image_fields'),
);
function lazy_field_types() {
return [
'colorbox',
'image',
];
}
/**
* Implements callback_filter_process().
* Implements template_preprocess_field().
*/
function _filter_lazy_process($text, $filter) {
$opt_skipClass = variable_get('lazy_filter_skipClass');
$opt_selector = ltrim(variable_get('lazy_filter_selector'), '.');
$opt_tags = variable_get('lazy_filter_alter_tag');
$opt_src = (variable_get('lazy_filter_src') !== 'src') ? variable_get('lazy_filter_src') : 'data-filterlazy-src';
$opt_placeholderSrc = variable_get('lazy_filter_placeholderSrc');
$html_dom = filter_dom_load($text);
foreach ($opt_tags as $tag) {
$matches = $html_dom->getElementsByTagName($tag);
foreach ($matches as $element) {
$classes = $element->getAttribute('class');
$classes = (strlen($classes) > 0) ? explode(' ', $classes) : [];
if (!in_array($opt_skipClass, $classes)) {
$classes[] = $opt_selector;
$element->setAttribute('class', implode(' ', $classes));
$src = $element->getAttribute('src');
$element->removeAttribute('src');
$element->setAttribute($opt_src, $src);
$element->setAttribute('src', $opt_placeholderSrc);
function lazy_preprocess_field(&$variables) {
$element = $variables['element'];
if ($element['#field_type'] === 'image') {
$config = \Drupal::config('lazy.settings')->get();
$pages = $config['disabled_paths'];
$path_matches = lazy_disabled_by_path($pages);
if (!$path_matches) {
$entity = $element['#object'];
$entity_type = $entity->getEntityTypeId();
$bundle = $entity->bundle();
$view_mode = $element['#view_mode'];
$field_name = $element['#field_name'];
$render_display = EntityViewDisplay::collectRenderDisplay($entity, $view_mode);
$field_display = $render_display->getComponent($field_name);
$image_fields = [];
if (isset($field_display['third_party_settings']['lazy']) && $field_display['third_party_settings']['lazy']['lazy_image']) {
foreach ($variables['items'] as $key => $item) {
$variables['items'][$key]['content']['#item_attributes']['data-lazy-load-fields'] = 'image';
$view_mode = ($view_mode === 'full') ? 'default' : $view_mode;
$image_fields["$entity_type--$bundle--$view_mode--$field_name"] = TRUE;
}
}
lazy_settings_update($image_fields);
}
}
$text = filter_dom_serialize($html_dom);
return trim($text);
}
/**
* Implements callback_filter_tips().
* Implements template_preprocess_image().
*/
function _filter_lazy_tips($filter, $format, $long = FALSE) {
$tags = variable_get('lazy_filter_alter_tag');
$tags = implode(' ', $tags);
$tags = trim(str_replace(array('0'), '', $tags));
switch ($tags) {
case 'img iframe':
return t('Both %img and %iframe elements are lazy-loaded.', array('%img' => '<img>', '%iframe' => '<iframe>'));
case 'img':
return t('%img elements are lazy-loaded.', array('%img' => '<img>', '%iframe' => '<iframe>'));
case 'iframe':
return t('%iframe elements are lazy-loaded.', array('%img' => '<img>', '%iframe' => '<iframe>'));
function lazy_preprocess_image(&$variables) {
if (array_key_exists('data-lazy-load-fields', $variables['attributes'])) {
unset($variables['attributes']['data-lazy-load-fields']);
$config = \Drupal::config('lazy.settings')->get();
$variables['attributes'][$config['src']] = $variables['attributes']['src'];
$variables['attributes']['src'] = $config['placeholderSrc'];
$variables['attributes']['class'][] = $config['selector'];
}
}
/**
* Is filter enabled for any of text-formats.
* Implements hook_page_attachments().
*/
function lazy_is_filter_enabled() {
$options = array(
':module' => 'lazy',
':name' => 'lazy_filter',
':status' => 1,
);
$enabled_formats = db_query('SELECT f.format, f.settings FROM {filter} f WHERE f.module = :module AND f.name = :name AND f.status = :status', $options)->fetchAllKeyed();
return count($enabled_formats) ? TRUE : FALSE;
function lazy_page_attachments(array &$attachments) {
$config = lazy_is_enabled();
if ($config) {
$options = [
'errorClass' => $config['errorClass'],
'loadInvisible' => (bool) $config['loadInvisible'],
'offset' => (int) $config['offset'],
'saveViewportOffsetDelay' => (int) $config['saveViewportOffsetDelay'],
'selector' => '.' . $config['selector'],
'skipClass' => $config['skipClass'],
'src' => $config['src'],
'successClass' => $config['successClass'],
'validateDelay' => $config['validateDelay'],
'placeholderSrc' => $config['placeholderSrc'],
];
$attachments['#attached']['library'][] = 'lazy/lazy';
$attachments['#attached']['drupalSettings']['lazy'] = $options;
}
}
/**
* Implements hook_page_build().
* Callback function to check whether lazy is enabled in any text-formats.
*
* @return mixed
* Lazy configuration object if enabled, false otherwise.
*/
function lazy_page_build(&$page) {
$filter_enabled = lazy_is_filter_enabled();
$field_enabled = variable_get('lazy_filter_image_fields');
function lazy_is_enabled() {
$status = [];
$filters = filter_formats();
foreach ($filters as $key => $filter) {
if (
$filter->status()
&& isset($filter->getDependencies()['module'])
&& in_array('lazy', $filter->getDependencies()['module'], TRUE)
) {
$status[$key] = TRUE;
}
}
if ($filter_enabled || $field_enabled) {
$path = drupal_get_path('module', 'lazy');
$config = \Drupal::config('lazy.settings')->get();
$image_fields = $config['image_fields'];
if (is_array($image_fields)) {
foreach ($image_fields as $field_name => $bool_value) {
if ($bool_value) {
$status[$field_name] = TRUE;
}
}
}
$page['page_bottom']['lazy'] = array(
'#attached' => array(),
);
$attached = &$page['page_bottom']['lazy']['#attached'];
$config['status'] = $status;
$settings = array(
'errorClass' => variable_get('lazy_filter_errorClass'),
'loadInvisible' => variable_get('lazy_filter_loadInvisible'),
'offset' => intval(variable_get('lazy_filter_offset')),
'saveViewportOffsetDelay' => intval(variable_get('lazy_filter_saveViewportOffsetDelay')),
'selector' => '.' . variable_get('lazy_filter_selector'),
'src' => variable_get('lazy_filter_src'),
'successClass' => variable_get('lazy_filter_successClass'),
'validateDelay' => intval(variable_get('lazy_filter_validateDelay')),
);
$attached['js'][] = array(
'data' => array('lazy' => array('bLazy' => $settings)),
'type' => 'setting',
);
$attached['libraries_load'][] = array('blazy');
$attached['js'][$path . '/lazy.js'] = array('every_page' => TRUE);
}
return count($status) ? $config : FALSE;
}
/**
* Implements hook_libraries_info().
* Implements hook_field_formatter_third_party_settings_form().
*/
function lazy_libraries_info() {
$libraries['blazy'] = array(
'name' => 'bLazy',
'vendor url' => 'https://github.com/dinbror/blazy/',
'download url' => 'https://github.com/dinbror/blazy/archive/master.zip',
'version callback' => 'lazy_library_get_version',
'version arguments' => array(
'file' => 'package.json',
),
'files' => array(
'js' => array(
'blazy.min.js' => array(
'group' => JS_LIBRARY,
'every_page' => TRUE,
),
),
),
'variants' => array(
'source' => array(
'files' => array(
'js' => array(
'blazy.js' => array(
'group' => JS_LIBRARY,
'every_page' => TRUE,
),
),
),
),
),
);
function lazy_field_formatter_third_party_settings_form($plugin) {
$element = [];
if (in_array($plugin->getPluginId(), lazy_field_types(), TRUE)) {
$element['lazy_image'] = [
'#type' => 'checkbox',
'#title' => t('Enable lazy-loading'),
'#default_value' => $plugin
->getThirdPartySetting('lazy', 'lazy_image', FALSE),
];
}
return $libraries;
return $element;
}
/**
* Callback function to return version from provided JSON file.
* Implements hook_form_FORM_BASE_ID_alter().
*/
function lazy_library_get_version($library, $options) {
$file = DRUPAL_ROOT . '/' . $library['library path'] . '/' . $options['file'];
if (empty($options['file']) || !file_exists($file)) {
return;
function lazy_form_entity_view_display_edit_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$entity_type = $form['#entity_type'];
$bundle = $form['#bundle'];
$image_fields = [];
if ($fields = $form_state->getValue('fields')) {
foreach ($fields as $field_name => $field) {
if (
in_array($field['type'], lazy_field_types(), TRUE)
&& isset($field['settings_edit_form']['third_party_settings']['lazy']['lazy_image'])
) {
$route_match = \Drupal::service('current_route_match');
$view_mode = $route_match->getParameter('view_mode_name');
$image_fields["$entity_type--$bundle--$view_mode--$field_name"] = (bool) $field['settings_edit_form']['third_party_settings']['lazy']['lazy_image'];
}
}
}
$file_content = file_get_contents($file);
$json = json_decode($file_content, TRUE);
return $json['version'];
lazy_settings_update($image_fields);
}
/**
* Implements hook_theme_registry_alter().
* Implements hook_field_formatter_settings_summary_alter().
*/
function lazy_theme_registry_alter(&$theme_registry) {
if (variable_get('lazy_filter_image_fields') && isset($theme_registry['image'])) {
$theme_registry['image']['function'] = 'theme_lazy_image';
function lazy_field_formatter_settings_summary_alter(&$summary, $context) {
if (
in_array($context['formatter']->getPluginId(), lazy_field_types(), TRUE)
&& $context['formatter']->getThirdPartySetting('lazy', 'lazy_image', FALSE)
) {
$summary[] = t('Lazy-loading enabled');
}
}
/**
* Overrides theme_image().
* Update `lazy.settings.image_fields` value with currently enabled options.
*
* @param array $image_fields
* An array of image fields set to use Lazy.
*/
function theme_lazy_image($variables) {
$opt_skipClass = variable_get('lazy_filter_skipClass');
$opt_selector = ltrim(variable_get('lazy_filter_selector'), '.');
$opt_src = (variable_get('lazy_filter_src') !== 'src') ? variable_get('lazy_filter_src') : 'data-filterlazy-src';
$opt_placeholderSrc = variable_get('lazy_filter_placeholderSrc');
// Skip Blazy rendering if variables contain `.no-b-lazy` CSS class name.
if (!empty($variables['attributes']['class']) && in_array($opt_skipClass, $variables['attributes']['class'])) {
return theme('image', $variables);
function lazy_settings_update(array $image_fields) {
$current_image_fields = \Drupal::config('lazy.settings')->get('image_fields');
if (!is_array($current_image_fields) || empty($current_image_fields)) {
$current_image_fields = [];
}
else {
// $defaults = _filter_lazy_defaults();
$attributes = $variables['attributes'];
$attributes['src'] = $opt_placeholderSrc;
$attributes[$opt_src] = file_create_url($variables['path']);
$attributes['class'][] = $opt_selector;
foreach (array(
'width',
'height',
'alt',
'title',
) as $key) {
if (isset($variables[$key])) {
$attributes[$key] = $variables[$key];
}
$fields = array_merge($current_image_fields, $image_fields);
foreach ($fields as $field_name => $bool_value) {
if (!$bool_value) {
unset($fields[$field_name]);
}
return '<img' . drupal_attributes($attributes) . ' />';
}
\Drupal::service('config.factory')->getEditable('lazy.settings')
->set('image_fields', $fields)
->save();
}
/**
* Checks whether lazy-load is disabled for the current path.
*
* @param string $disabled_paths
* List of paths Lazy should be disabled.
*
* @return bool
* Whether Lazy is disabled for the requested path.
*/
function lazy_disabled_by_path($disabled_paths) {
if (empty($disabled_paths)) {
return FALSE;
}
// Convert path to lowercase. This allows comparison of the same path
// with different case. Ex: /Page, /page, /PAGE.
$disabled_paths = mb_strtolower($disabled_paths);
// Current path. Do not trim a trailing slash if that is the complete path.
$current_path = \Drupal::service('path.current')->getPath();
$current_path = $current_path === '/' ? $current_path : rtrim($current_path, '/');
// Path alias.
$path_alias = \Drupal::service('path.alias_manager')->getAliasByPath($current_path);
$path_alias = mb_strtolower($path_alias);
// If either returns TRUE, Lazy should be disabled for this path.
$path_matcher = \Drupal::service('path.matcher')->matchPath($current_path, $disabled_paths);
$path_alias_matcher = \Drupal::service('path.matcher')->matchPath($path_alias, $disabled_paths);
return ($path_alias_matcher || $path_matcher);
}
lazy.config_form:
path: 'admin/config/content/lazy'
defaults:
_title: 'Lazy-load'
_form: 'Drupal\lazy\Form\LazyForm'
requirements:
_permission: 'administer filters'
<?php
namespace Drupal\lazy\Form;
use Drupal\Core\Form\ConfigFormBase;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Url;
use Drupal\Core\Messenger\MessengerInterface;
/**
* Configure Lazy settings for this site.
*/
class LazyForm extends ConfigFormBase {
/**
* {@inheritdoc}
*/
public function getFormId() {
return 'lazy_form';
}
/**
* {@inheritdoc}
*/
protected function getEditableConfigNames() {
return ['lazy.settings'];
}
/**
* Gets enabled filter formats.
*
* @param array|null $status
* Array of filter-formats and entity bundles.
*/
protected function getEnabledFiltersAndFields($status) {
$links = [];
$anchor_links = [];
if (!empty($status)) {
foreach ($status as $name => $bool_value) {
if ($bool_value && is_string($name)) {
if ((strpos($name, 'filter.format.') === 0) && ($filter_format = substr($name, 14))) {
$links['filter'][$filter_format] = [
'uri' => Url::fromRoute('entity.filter_format.edit_form', [
'filter_format' => $filter_format,
])->toString(),
'title' => $filter_format,
];
}
elseif (($entity = explode('--', $name)) && count($entity)) {
list($entity_type, $entity_bundle, $view_mode) = $entity;
if (($entity_type !== NULL) && ($entity_bundle !== NULL)) {
$links['field']["$entity_type--$entity_bundle--$view_mode"] = [
'uri' => Url::fromRoute("entity.entity_view_display.$entity_type.view_mode", [
(($entity_type === 'paragraph') ? 'paragraphs' : $entity_type) . '_type' => $entity_bundle,
'view_mode_name' => $view_mode,
])->toString(),
'title' => $entity_bundle . " ($view_mode)",
];
}
}
}
}
foreach ($links as $key => $link_group) {
foreach ($link_group as $link) {
$anchor_links[$key][] = '<a href="' . $link['uri'] . '">' . $link['title'] . '</a>';
}
}
}
$filter_links_result = (isset($anchor_links['filter']) && count($anchor_links['filter'])) ? implode(', ', $anchor_links['filter']) : 'none';
$field_links_result = (isset($anchor_links['field']) && count($anchor_links['field'])) ? implode(', ', $anchor_links['field']) : 'none';
$filter_message_type = ($filter_links_result === 'none') ? MessengerInterface::TYPE_WARNING : MessengerInterface::TYPE_STATUS;
$this->messenger()->addMessage($this->t("The <strong>text-formats</strong> have lazy-loading enabled: $filter_links_result"), $filter_message_type);
$field_message_type = ($field_links_result === 'none') ? MessengerInterface::TYPE_WARNING : MessengerInterface::TYPE_STATUS;
$this->messenger()->addMessage($this->t("The <strong>image fields</strong> have lazy-loading enabled: $field_links_result"), $field_message_type);
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, FormStateInterface $form_state) {
$config = $this->config('lazy.settings');
$filter_enabled = lazy_is_enabled();
$this->getEnabledFiltersAndFields($filter_enabled['status']);
if (!$filter_enabled) {
$this->messenger()->addWarning($this->t('<p>The %filter filter must be enabled for at least one <a href=":path">text-format</a>, or one entity image:</p>', [
':path' => Url::fromRoute('filter.admin_overview')->toString(),
'%filter' => 'Lazy-load',
]));
}
$form['settings'] = [
'#type' => 'fieldset',
'#title' => $this->t('Settings'),
'#open' => TRUE,
];
$form['settings']['alter_tag'] = [
'#type' => 'checkboxes',
'#title' => $this->t('Select the inline elements to be lazy-loaded via filter.'),
'#description' => $this->t('Only selected tags will be lazy-loaded in activated text-formats.'),
'#options' => [
'img' => $this->t('Enable for images (%img tags)', ['%img' => '<img>']),
'iframe' => $this->t('Enable for iframes (%iframe tags)', ['%iframe' => '<iframe>']),
],
'#default_value' => $config->get('alter_tag'),
'#disabled' => !$filter_enabled,
];
$form['settings']['placeholderSrc'] = [
'#type' => 'textfield',
'#title' => $this->t('Placeholder image URL'),
'#default_value' => $config->get('placeholderSrc'),
'#maxlength' => 255,
'#required' => TRUE,
];
$form['paths'] = array(
'#type' => 'details',
'#title' => $this->t('Disabled pages'),
'#description' => $this->t('Lazy-loading is disabled for both <em>image fields</em> and <em>inline images/iframes</em> on following pages.'),
'#open' => FALSE,
);
$form['paths']['disabled_paths'] = array(
'#type' => 'textarea',
'#title' => $this->t('Pages'),
'#default_value' => $config->get('disabled_paths'),
'#description' => $this->t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. An example path is %user-wildcard for every user page. %front is the front page.", [
'%user-wildcard' => '/user/*',
'%front' => '<front>',
]),
);
$form['blazy'] = [
'#type' => 'details',
'#title' => $this->t('bLazy configuration'),
'#description' => $this->t('<p><a href=":url">bLazy</a> is a lightweight lazy loading and multi-serving image script created by Bjoern Klinggaard. See its website for usage details and demos.</p>', [
':url' => 'http://dinbror.dk/blog/blazy/',
]),
'#open' => FALSE,
];
$form['blazy']['loadInvisible'] = [
'#type' => 'checkbox',
'#title' => $this->t('loadInvisible'),
'#description' => $this->t('If checked loads invisible (hidden) elements.'),
'#default_value' => $config->get('loadInvisible'),
'#return_value' => TRUE,
];
$form['blazy']['offset'] = [
'#type' => 'number',
'#title' => $this->t('offset'),
'#description' => $this->t('The offset controls how early you want the elements to be loaded before they’re visible.'),
'#default_value' => $config->get('offset'),
'#min' => 0,
'#required' => TRUE,
];
$form['blazy']['saveViewportOffsetDelay'] = [
'#type' => 'number',
'#title' => $this->t('saveViewportOffsetDelay'),
'#description' => $this->t('Delay for how often it should call the saveViewportOffset function on resize.'),
'#default_value' => $config->get('saveViewportOffsetDelay'),
'#min' => 0,
'#required' => TRUE,
];
$form['blazy']['validateDelay'] = [
'#type' => 'number',
'#title' => $this->t('validateDelay'),
'#description' => $this->t('Delay for how often it should call the validate function on scroll/resize.'),
'#default_value' => $config->get('validateDelay'),
'#min' => 0,
'#required' => TRUE,
];
$form['blazy']['selector'] = [
'#type' => 'textfield',
'#title' => $this->t('Selector class'),
'#description' => $this->t('Element selector for elements that should lazy load. Do not include a leading period.'),
'#default_value' => $config->get('selector'),
'#required' => TRUE,
];
$form['blazy']['skipClass'] = [
'#type' => 'textfield',
'#title' => $this->t('skipClass'),
'#description' => $this->t('Elements having this class name will be ignored.'),
'#default_value' => $config->get('skipClass'),
'#required' => TRUE,
];
$form['blazy']['errorClass'] = [
'#type' => 'textfield',
'#title' => $this->t('errorClass'),
'#description' => $this->t('The classname an element will get if something goes wrong.'),
'#default_value' => $config->get('errorClass'),
'#required' => TRUE,
];
$form['blazy']['successClass'] = [
'#type' => 'textfield',
'#title' => $this->t('successClass'),
'#description' => $this->t('The classname an element will get when loaded.'),
'#default_value' => $config->get('successClass'),
'#required' => TRUE,
];
$form['blazy']['src'] = [
'#type' => 'textfield',
'#title' => $this->t('src'),
'#description' => $this->t('Attribute where the original element source will be assigned. Do not change this unless attribute is used for other purposes.'),
'#default_value' => $config->get('src'),
'#required' => TRUE,
];
$form['clear_cache'] = [
'#type' => 'checkbox',
'#title' => $this->t('Check the box to clear the cache'),
];
return parent::buildForm($form, $form_state);
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
$this->config('lazy.settings')
->set('alter_tag', $form_state->getValue('alter_tag'))
->set('disabled_paths', $form_state->getValue('disabled_paths'))
->set('errorClass', $form_state->getValue('errorClass'))
->set('loadInvisible', (bool) $form_state->getValue('loadInvisible'))
->set('offset', (int) $form_state->getValue('offset'))
->set('saveViewportOffsetDelay', (int) $form_state->getValue('saveViewportOffsetDelay'))
->set('selector', $form_state->getValue('selector'))
->set('skipClass', $form_state->getValue('skipClass'))
->set('src', $form_state->getValue('src'))
->set('successClass', $form_state->getValue('successClass'))
->set('validateDelay', $form_state->getValue('validateDelay'))
->set('placeholderSrc', $form_state->getValue('placeholderSrc'))
->save();
parent::submitForm($form, $form_state);
if ($form_state->getValue('clear_cache')) {
$this->cacheClear();
}
}
/**
* Clears all caches, then redirects to the previous page.
*/
public function cacheClear() {
drupal_flush_all_caches();
$this->messenger()->addMessage('Cache cleared.');
}
}
<?php
namespace Drupal\lazy\Plugin\Filter;
use Drupal\Component\Utility\Html;
use Drupal\filter\FilterProcessResult;
use Drupal\filter\Plugin\FilterBase;
/**
* Provides a filter to lazy-load images.
*
* @Filter(
* id = "lazy_filter",
* title = @Translation("Lazy-load images and IFRAMEs via bLazy"),
* description = @Translation("<a href=':url'>Configure options</a>", arguments = {":url" = "/admin/config/content/lazy"}),
* type = Drupal\filter\Plugin\FilterInterface::TYPE_TRANSFORM_REVERSIBLE,
* weight = 20
* )
*/
class LazyFilter extends FilterBase {
/**
* {@inheritdoc}
*/
public function process($text, $langcode) {
$config = \Drupal::config('lazy.settings')->get();
$opt_skipClass = $config['skipClass'];
$opt_selector = ltrim($config['selector'], '.');
$opt_tags = $config['alter_tag'];
$opt_src = ($config['src'] !== 'src') ? $config['src'] : 'data-filterlazy-src';
$opt_placeholderSrc = $config['placeholderSrc'];
$result = new FilterProcessResult($text);
$html_dom = Html::load($text);
$pages = $config['disabled_paths'];
$path_matches = lazy_disabled_by_path($pages);
if (!$path_matches) {
foreach ($opt_tags as $tag => $status) {
$matches = $html_dom->getElementsByTagName($tag);
foreach ($matches as $element) {
$classes = $element->getAttribute('class');
$classes = ($classes !== '') ? explode(' ', $classes) : [];
$parent_classes = $element->parentNode->getAttribute('class');
$parent_classes = ($parent_classes !== '') ? explode(' ', $parent_classes) : [];
if (empty($opt_tags[$tag])) {
// If the `tag` is not enabled remove the bLazy selector class.
if (($key = array_search($opt_selector, $classes, FALSE)) !== FALSE) {
unset($classes[$key]);
$element->setAttribute('class', implode(' ', $classes));
if (empty($classes)) {
$element->removeAttribute('class');
}
}
}
else {
// `tag` is enabled. Make sure skipClass is not set before
// proceeding.
if (!in_array($opt_skipClass, $classes, FALSE) && !in_array($opt_skipClass, $parent_classes, FALSE)) {
$classes[] = $opt_selector;
$classes = array_unique($classes);
$element->setAttribute('class', implode(' ', $classes));
$src = $element->getAttribute('src');
$element->removeAttribute('src');
$element->setAttribute($opt_src, $src);
$element->setAttribute('src', $opt_placeholderSrc);
}
}
}
}
}
$result->setProcessedText(Html::serialize($html_dom));
return $result;
}
/**
* {@inheritdoc}
*/
public function tips($long = FALSE) {
$settings = \Drupal::config('lazy.settings');
$tags = $settings->get('alter_tag');
$skip = $settings->get('skipClass');
$options = ['%img' => '<img>', '%iframe' => '<iframe>'];
$skip_help = t('If you want certain elements skip lazy-loading, add <code>%class</code> class name.', ['%class' => $skip]);
if (!empty($tags)) {
if ($tags['img'] && $tags['iframe']) {
return t('Lazy-loading is enabled for both %img and %iframe tags.', $options) . ' ' . $skip_help;
}
elseif ($tags['img']) {
return t('Lazy-loading is enabled for %img tags.', $options) . ' ' . $skip_help;
}
elseif ($tags['iframe']) {
return t('Lazy-loading is enabled for %iframe tags.', $options) . ' ' . $skip_help;
}
}
return t('Lazy-loading is not enabled.');
}
}