Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Close #2903 Add Image Zoom #3596

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
8 changes: 8 additions & 0 deletions modules/custom/az_zoom/az_zoom.info.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
name: Quickstart Zoom
type: module
description: Provides field formatter for image fields to enlarge on touch, click, or mouseover.
core_version_requirement: ^9.3 || ^10 || ^11
dependencies:
- drupal:image
- drupal:responsive_image
- drupal:field
22 changes: 22 additions & 0 deletions modules/custom/az_zoom/az_zoom.libraries.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
zoom:
version: 1.x
css:
theme:
css/az-zoom.css: {}
js:
js/az-zoom.js:
attributes:
type: 'module'
dependencies:
- core/drupal
- core/drupalSettings

drupal:
version: 1.x
js:
js/az-zoom.drupal.js:
attributes:
type: 'module'

dependencies:
- az_zoom/zoom
27 changes: 27 additions & 0 deletions modules/custom/az_zoom/az_zoom.module
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

/**
* @file
* AZ Zoom module.
*/

/**
* Implements hook_theme().
*/
function az_zoom_theme() {
return [
'az_zoom' => [
'variables' => [
'images' => NULL,
],
'template' => 'az-zoom',
],
];
}

/**
* Implements hook_preprocess_az_zoom().
*/
function template_preprocess_az_zoom(&$variables) {
$variables['#attached']['library'][] = 'az_zoom/drupal';
}
18 changes: 18 additions & 0 deletions modules/custom/az_zoom/css/az-zoom.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
.image-zoom {
display: none;
}
.image-zoom-container {
position: relative;
overflow: hidden;
}

.image-zoom-container:after {
content: '';
display: block;
width: 33px;
height: 33px;
position: absolute;
top: 0;
right: 0;
background: url(../images/icon.png);
}
Binary file added modules/custom/az_zoom/images/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions modules/custom/az_zoom/js/az-zoom.drupal.es6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// @file az-zoom.drupal.js

import FocusImg from './az-zoom.js';

((Drupal, drupalSettings) => {
Drupal.behaviors.zoomEffect = {
attach: function (context, drupalSettings) {
context.querySelectorAll('.image-zoom-container').forEach((element) => {
// Find the <img> tag within the .image-zoom-container
let imgElement = element.querySelector('.original-image');
let fid = imgElement.getAttribute('fid');
new FocusImg({
imageSrc: drupalSettings.AZZoom.image_urls[fid] || imgElement.src,
parentElement: element,
zoomFactor: drupalSettings.AZZoom.image_zoom_factor || '250%',
smoother: drupalSettings.AZZoom.image_smoother || true,
width: drupalSettings.AZZoom.image_width || '100%',
height: drupalSettings.AZZoom.image_height || '66.7%',
displayLoc: drupalSettings.AZZoom.display_loc || false,
displayZoom: drupalSettings.AZZoom.display_zoom || false,
zoomOnScroll: drupalSettings.AZZoom.zoom_on_scroll || false,
});
});
},
};
})(Drupal, drupalSettings);
28 changes: 28 additions & 0 deletions modules/custom/az_zoom/js/az-zoom.drupal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* DO NOT EDIT THIS FILE.
* See the following change record for more information,
* https://www.drupal.org/node/2815083
* @preserve
**/
import FocusImg from './az-zoom.js';
(function (Drupal, drupalSettings) {
Drupal.behaviors.zoomEffect = {
attach: function attach(context, drupalSettings) {
context.querySelectorAll('.image-zoom-container').forEach(function (element) {
var imgElement = element.querySelector('.original-image');
var fid = imgElement.getAttribute('fid');
new FocusImg({
imageSrc: drupalSettings.AZZoom.image_urls[fid] || imgElement.src,
parentElement: element,
zoomFactor: drupalSettings.AZZoom.image_zoom_factor || '250%',
smoother: drupalSettings.AZZoom.image_smoother || true,
width: drupalSettings.AZZoom.image_width || '100%',
height: drupalSettings.AZZoom.image_height || '66.7%',
displayLoc: drupalSettings.AZZoom.display_loc || false,
displayZoom: drupalSettings.AZZoom.display_zoom || false,
zoomOnScroll: drupalSettings.AZZoom.zoom_on_scroll || false
});
});
}
};
})(Drupal, drupalSettings);
204 changes: 204 additions & 0 deletions modules/custom/az_zoom/js/az-zoom.es6.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/**
* An image component that supports the zooming effect for any web app.
*/

class FocusImg {
/**
* Constructor for the focus image component
* @param {FocusImgConfig} imageParams - {
* imageSrc: String,
* parentElement: HTMLElement,
* zoomFactor: String,
* smoother: boolean,
* width: String,
* height: String
* }
*/
constructor(imageParams) {
this.DEFAULT_IMAGE_SRC = 'http://via.placeholder.com/500?text=focus.js';

// Initialize default properties for image
this.params = {
imageSrc: this.DEFAULT_IMAGE_SRC,
parentElement: null,
zoomFactor: '250%',
smoother: true,
width: '100%', // Scale to parent component by default
height: '66.7%', // Scale to percent of height by default
cursor: '', // Blank for default hand cursor
displayLoc: false, // Displays the dimensions hud
displayZoom: false, // Displays the zoom hud
zoomOnScroll: false, // Enable scrolling for zooming image
};

this.focusImg = document.createElement('div');
this.focusImg.style.position = 'relative';

// Apply given params to the object
if (imageParams) Object.assign(this.params, imageParams);

this.render();
this.bindEvents();

// Initialize control add-ons
this.displayLocHud = this.params.displayLoc
? document.createElement('span')
: null;
this.displayZoomHud = this.params.displayZoom
? document.createElement('span')
: null;

if (this.params.displayLoc) {
this.displayLocHud.classList.add('hud', 'hud-bottom-right');
this.focusImg.appendChild(this.displayLocHud);
}

if (this.params.displayZoom) {
this.displayZoomHud.classList.add('hud', 'hud-bottom-left');
this.focusImg.appendChild(this.displayZoomHud);
}

return this;
}

/**
* Binds events to the current image component.
*/
bindEvents() {
// Zoom in on hover
this.focusImg.addEventListener(
'mouseover',
(e) => {
this.focusImg.getElementsByClassName(
'focus-img'
)[0].style.backgroundSize = this.params.zoomFactor;
},
false
);

// Pan the image proportional to the cursor location
this.focusImg.addEventListener(
'mousemove',
(e) => {
let dimensions = this.focusImg.getBoundingClientRect(); // Get client rectangle of the element on thepage

// Calculate location of cursor inside the element
this.relX = e.clientX - dimensions.left;
this.relY = e.clientY - dimensions.top;

// Calculate the cursor position as a percentage of the image
this.percentX = Math.round(100 / (dimensions.width / this.relX));
this.percentY = Math.round(100 / (dimensions.height / this.relY));

// Update the image background position
this.focusImg.getElementsByClassName(
'focus-img'
)[0].style.backgroundPosition =
this.percentX + '% ' + this.percentY + '%';

// Update HUD info if needed
if (this.params.displayLoc) this.updateLocHud();
if (this.params.displayZoom) this.updateZoomHud();
},
false
);

// Revert image view back to normal after mouse exits
this.focusImg.addEventListener(
'mouseleave',
(e) => {
this.focusImg.getElementsByClassName(
'focus-img'
)[0].style.backgroundPosition = 'center';
this.focusImg.getElementsByClassName(
'focus-img'
)[0].style.backgroundSize = 'cover';
},
false
);

this.focusImg.addEventListener('wheel', (e) => {
if (!this.params.zoomOnScroll) return;

e.preventDefault();
const curZoom = parseInt(
this.focusImg
.getElementsByClassName('focus-img')[0]
.style.backgroundSize.replace('%', '')
);

// Set bounds
if (curZoom <= 40 && e.deltaY > 0) {
this.params.zoomFactor = `40%`;
return;
}

if (curZoom >= 1000 && e.deltaY < 0) {
this.params.zoomFactor = `1000%`;
return;
}

this.params.zoomFactor = `${curZoom + (e.deltaY <= 0 ? 10 : -10)}%`;
this.focusImg.getElementsByClassName(
'focus-img'
)[0].style.backgroundSize = `${this.params.zoomFactor}`;
this.updateZoomHud();
});
}

/**
* Render the component onto the page
*/
render() {
this.createFocusImgElement();
this.setStyleForFocusImg();
this.appendToParent();
}

/**
* Creates the focus image element with appropriate classes and styles
*/
createFocusImgElement() {
const imageElementClass = `focus-img ${
this.params.smoother ? 'smoother' : ''
} ${this.params.cursor ? this.params.cursor : ''}`;
const imageElementStyle = `
background-image: url(${this.params.imageSrc});
background-repeat: no-repeat;
background-size: cover;
background-position: center center;
width: 100%;
padding-top: ${this.params.height};
`;

this.focusImg.innerHTML = `<div class="${imageElementClass}" style="${imageElementStyle}"></div>`;
}

/**
* Sets the width of the focus image
*/
setStyleForFocusImg() {
this.focusImg.style.width = this.params.width;
}

/**
* Appends the focus image to the parent element
*/
appendToParent() {
this.params.parentElement.appendChild(this.focusImg);
}
/**
* HELPERS
*/
updateLocHud() {
this.displayLocHud.innerHTML = `${Math.floor(this.relX) || 0}, ${
Math.floor(this.relY) || 0
}`;
}

updateZoomHud() {
this.displayZoomHud.innerHTML = `${this.params.zoomFactor}`;
}
}

export default FocusImg;
Loading