Search code examples
javascripthtmljquerycssjquery-plugins

Need help implementing EasyZoom jquery plugin


I'm not too familiar with jquery and javascript in general, so bear with me here. But, I'm trying to create an image gallery with zoom on hover using EasyZoom:https://i-like-robots.github.io/EasyZoom/. I want to achieve something like what is under the "with thumbnail images" in the link.

So far, I've created the divs with images in them, copied the CSS and javascript files from GitHub, and linked my HTML page to them, see specific code below.

But, I'm obviously missing something here. In the JS file, I found a spot where it said @param {Object} target am I supposed to specify the objects in the HTML where there needs to be Easyzoom? If so, how? I'm also confused as to how i can specify that I want an image gallery with Easyzoom. So, if anyone could help me with this, it would be great.

HTML code (I have a list of image links that I got from a Django model under 'images')

       {% for image in images %}
           <div class="slides">
               <div class="easyzoom">
                   <a href="{{image}}">
                       <img src="{{image}}">
                   </a>
               </div>
           </div>
       {% endfor %}

Easyzoom.css

/**
 * EasyZoom core styles
 */
 .easyzoom {
    position: relative;

    /* 'Shrink-wrap' the element */
    display: inline-block;
    *display: inline;
    *zoom: 1;
}

.easyzoom img {
    vertical-align: bottom;
}

.easyzoom.is-loading img {
    cursor: progress;
}

.easyzoom.is-ready img {
    cursor: crosshair;
}

.easyzoom.is-error  img {
    cursor: not-allowed;
}

.easyzoom-notice {
    position: absolute;
    top: 50%;
    left: 50%;
    z-index: 150;
    width: 10em;
    margin: -1em 0 0 -5em;
    line-height: 2em;
    text-align: center;
    background: #FFF;
    box-shadow: 0 0 10px #888;
}

.easyzoom-flyout {
    position:absolute;
    z-index: 100;
    overflow: hidden;
    background: #FFF;
}

Easyzoom.js

(function (root, factory) {
    'use strict';
    if(typeof define === 'function' && define.amd) {
        define(['jquery'], function($){
            factory($);
        });
    } else if(typeof module === 'object' && module.exports) {
        module.exports = (root.EasyZoom = factory(require('jquery')));
    } else {
        root.EasyZoom = factory(root.jQuery);
    }
}(this, function ($) {

    'use strict';

    var zoomImgOverlapX;
    var zoomImgOverlapY;
    var ratioX;
    var ratioY;
    var pointerPositionX;
    var pointerPositionY;

    var defaults = {

        // The text to display within the notice box while loading the zoom image.
        loadingNotice: 'Loading image',

        // The text to display within the notice box if an error occurs when loading the zoom image.
        errorNotice: 'The image could not be loaded',

        // The time (in milliseconds) to display the error notice.
        errorDuration: 2500,

        // Attribute to retrieve the zoom image URL from.
        linkAttribute: 'href',

        // Prevent clicks on the zoom image link.
        preventClicks: true,

        // Callback function to execute before the flyout is displayed.
        beforeShow: $.noop,

        // Callback function to execute before the flyout is removed.
        beforeHide: $.noop,

        // Callback function to execute when the flyout is displayed.
        onShow: $.noop,

        // Callback function to execute when the flyout is removed.
        onHide: $.noop,

        // Callback function to execute when the cursor is moved while over the image.
        onMove: $.noop

    };

    /**
     * EasyZoom
     * @constructor
     * @param {Object} target
     * @param {Object} options (Optional)
     */
    function EasyZoom(target, options) {
        this.$target = $(target);
        this.opts = $.extend({}, defaults, options, this.$target.data());

        this.isOpen === undefined && this._init();
    }

    /**
     * Init
     * @private
     */
    EasyZoom.prototype._init = function() {
        this.$link   = this.$target.find('a');
        this.$image  = this.$target.find('img');

        this.$flyout = $('<div class="easyzoom-flyout" />');
        this.$notice = $('<div class="easyzoom-notice" />');

        this.$target.on({
            'mousemove.easyzoom touchmove.easyzoom': $.proxy(this._onMove, this),
            'mouseleave.easyzoom touchend.easyzoom': $.proxy(this._onLeave, this),
            'mouseenter.easyzoom touchstart.easyzoom': $.proxy(this._onEnter, this)
        });

        this.opts.preventClicks && this.$target.on('click.easyzoom', function(e) {
            e.preventDefault();
        });
    };

    /**
     * Show
     * @param {MouseEvent|TouchEvent} e
     * @param {Boolean} testMouseOver (Optional)
     */
    EasyZoom.prototype.show = function(e, testMouseOver) {
        var self = this;

        if (this.opts.beforeShow.call(this) === false) return;

        if (!this.isReady) {
            return this._loadImage(this.$link.attr(this.opts.linkAttribute), function() {
                if (self.isMouseOver || !testMouseOver) {
                    self.show(e);
                }
            });
        }

        this.$target.append(this.$flyout);

        var targetWidth = this.$target.outerWidth();
        var targetHeight = this.$target.outerHeight();

        var flyoutInnerWidth = this.$flyout.width();
        var flyoutInnerHeight = this.$flyout.height();

        var zoomImgWidth = this.$zoom.width();
        var zoomImgHeight = this.$zoom.height();

        zoomImgOverlapX = Math.ceil(zoomImgWidth - flyoutInnerWidth);
        zoomImgOverlapY = Math.ceil(zoomImgHeight - flyoutInnerHeight);

        // For when the zoom image is smaller than the flyout element.
        if (zoomImgOverlapX < 0) zoomImgOverlapX = 0;
        if (zoomImgOverlapY < 0) zoomImgOverlapY = 0;

        ratioX = zoomImgOverlapX / targetWidth;
        ratioY = zoomImgOverlapY / targetHeight;

        this.isOpen = true;

        this.opts.onShow.call(this);

        e && this._move(e);
    };

    /**
     * On enter
     * @private
     * @param {Event} e
     */
    EasyZoom.prototype._onEnter = function(e) {
        var touches = e.originalEvent.touches;

        this.isMouseOver = true;

        if (!touches || touches.length == 1) {
            e.preventDefault();
            this.show(e, true);
        }
    };

    /**
     * On move
     * @private
     * @param {Event} e
     */
    EasyZoom.prototype._onMove = function(e) {
        if (!this.isOpen) return;

        e.preventDefault();
        this._move(e);
    };

    /**
     * On leave
     * @private
     */
    EasyZoom.prototype._onLeave = function() {
        this.isMouseOver = false;
        this.isOpen && this.hide();
    };

    /**
     * On load
     * @private
     * @param {Event} e
     */
    EasyZoom.prototype._onLoad = function(e) {
        // IE may fire a load event even on error so test the image dimensions
        if (!e.currentTarget.width) return;

        this.isReady = true;

        this.$notice.detach();
        this.$flyout.html(this.$zoom);
        this.$target.removeClass('is-loading').addClass('is-ready');

        e.data.call && e.data();
    };

    /**
     * On error
     * @private
     */
    EasyZoom.prototype._onError = function() {
        var self = this;

        this.$notice.text(this.opts.errorNotice);
        this.$target.removeClass('is-loading').addClass('is-error');

        this.detachNotice = setTimeout(function() {
            self.$notice.detach();
            self.detachNotice = null;
        }, this.opts.errorDuration);
    };

    /**
     * Load image
     * @private
     * @param {String} href
     * @param {Function} callback
     */
    EasyZoom.prototype._loadImage = function(href, callback) {
        var zoom = new Image();

        this.$target
            .addClass('is-loading')
            .append(this.$notice.text(this.opts.loadingNotice));

        this.$zoom = $(zoom)
            .on('error', $.proxy(this._onError, this))
            .on('load', callback, $.proxy(this._onLoad, this));

        zoom.style.position = 'absolute';
        zoom.src = href;
    };

    /**
     * Move
     * @private
     * @param {Event} e
     */
    EasyZoom.prototype._move = function(e) {

        if (e.type.indexOf('touch') === 0) {
            var touchlist = e.touches || e.originalEvent.touches;
            pointerPositionX = touchlist[0].pageX;
            pointerPositionY = touchlist[0].pageY;
        } else {
            pointerPositionX = e.pageX || pointerPositionX;
            pointerPositionY = e.pageY || pointerPositionY;
        }

        var targetOffset  = this.$target.offset();
        var relativePositionX = pointerPositionX - targetOffset.left;
        var relativePositionY = pointerPositionY - targetOffset.top;
        var moveX = Math.ceil(relativePositionX * ratioX);
        var moveY = Math.ceil(relativePositionY * ratioY);

        // Close if outside
        if (moveX < 0 || moveY < 0 || moveX > zoomImgOverlapX || moveY > zoomImgOverlapY) {
            this.hide();
        } else {
            var top = moveY * -1;
            var left = moveX * -1;

            this.$zoom.css({
                top: top,
                left: left
            });

            this.opts.onMove.call(this, top, left);
        }

    };

    /**
     * Hide
     */
    EasyZoom.prototype.hide = function() {
        if (!this.isOpen) return;
        if (this.opts.beforeHide.call(this) === false) return;

        this.$flyout.detach();
        this.isOpen = false;

        this.opts.onHide.call(this);
    };

    /**
     * Swap
     * @param {String} standardSrc
     * @param {String} zoomHref
     * @param {String|Array} srcset (Optional)
     */
    EasyZoom.prototype.swap = function(standardSrc, zoomHref, srcset) {
        this.hide();
        this.isReady = false;

        this.detachNotice && clearTimeout(this.detachNotice);

        this.$notice.parent().length && this.$notice.detach();

        this.$target.removeClass('is-loading is-ready is-error');

        this.$image.attr({
            src: standardSrc,
            srcset: $.isArray(srcset) ? srcset.join() : srcset
        });

        this.$link.attr(this.opts.linkAttribute, zoomHref);
    };

    /**
     * Teardown
     */
    EasyZoom.prototype.teardown = function() {
        this.hide();

        this.$target
            .off('.easyzoom')
            .removeClass('is-loading is-ready is-error');

        this.detachNotice && clearTimeout(this.detachNotice);

        delete this.$link;
        delete this.$zoom;
        delete this.$image;
        delete this.$notice;
        delete this.$flyout;

        delete this.isOpen;
        delete this.isReady;
    };

    // jQuery plugin wrapper
    $.fn.easyZoom = function(options) {
        return this.each(function() {
            var api = $.data(this, 'easyZoom');

            if (!api) {
                $.data(this, 'easyZoom', new EasyZoom(this, options));
            } else if (api.isOpen === undefined) {
                api._init();
            }
        });
    };

    return EasyZoom;
}));

The js and CSS files are basically exact copies of what is on Github here: https://github.com/i-like-robots/EasyZoom


Solution

  • As you are already using jQuery how about use Slick for your slider and integrate EasyZoom into your slick slides?

    Loads of cool features, check it... https://kenwheeler.github.io/slick/

    We can utilise slick's dots to manipulate dot buttons into image thumbnails, so no extra html in your original markup 👍🏼

    Read my comments in working example below and jsfiddle.

    Please note the easyzoom does not zoom very much because the demo images we are using are natively only 600px wide.

    jsFiddle version... https://jsfiddle.net/joshmoto/mdkpnw7g/

    Working stack example...

    // document ready
    $(document).ready(function() {
    
      // init ez-slider with slick
      $('.ez-slider').on('init', function(slick) {
    
        // slide var for use timeout
        const slider = this;
    
        // slight delay so slick init completes render
        setTimeout(function() {
    
          // init the easy zoom on slider 
          $('FIGURE', slider).addClass('easyzoom').easyZoom();
    
          // thumb buttons
          let thumbs = $('.slick-dots > LI > BUTTON', slider);
    
          // each thumbnail button function
          $.each(thumbs, function(i, e) {
    
            // slide id
            let slide_id = $(this).attr('aria-controls');
    
            // get thumbnail image src
            let thumb_img = $('#' + slide_id).find('IMG').attr('src');
    
            // change thumb button inner html too
            $(this).html('<img src="' + thumb_img + '" alt="" />');
    
          });
    
        }, 100);
    
      // then our slick options
      }).slick({
        slidesToShow: 1,
        slidesToScroll: 1,
        dots: true,
        arrows: false,
        adaptiveHeight: true,
        autoplay: false
      });
    
    });
    BODY {
      margin: 0;
      padding: 20px;
    }
    
    /* The minor fix I added to get demo working */
    
    .ez-slider .easyzoom .easyzoom-flyout {
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
    }
    
    /* Slide demo css not needed */
    
    .ez-slider {
      max-width: 400px;
      margin: 0 auto 20px auto !important;
    }
    
    .ez-slider FIGURE {
      margin: 0;
      display: block;
      position: relative;
      overflow: hidden;
    }
    
    .ez-slider FIGURE > A {
      height: auto;
      display: block;
      width: 100%;
    }
    
    .ez-slider FIGURE > A > IMG {
      height: auto;
      width: 100%;
      display: block;
    }
    
    .ez-slider .slick-dots {
      margin: 10px -5px 0 -5px;
      position: relative;
      bottom: 0;
      width: auto;
      display: block;
      
    }
    
    .ez-slider .slick-dots LI {
      width: calc(20% - 10px);
      height: auto;
      margin: 0 0 10px 0;
      padding: 0 5px 0 5px;
      display: inline-block;
    }
    
    .ez-slider .slick-dots LI.slick-active BUTTON,
    .ez-slider .slick-dots LI:hover BUTTON {
      opacity: 1;
    }
    
    .ez-slider .slick-dots LI BUTTON {
      overflow: hidden;
      position: relative;
      height: auto;
      padding: 0;
      transition: all .5s ease;
      width: 100%;
      opacity: .8;
    }
    
    .ez-slider .slick-dots LI BUTTON IMG {
      display: block;
      height: 100%;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%,-50%);
    }
    
    .ez-slider .slick-dots LI BUTTON:before {
      display: block;
      position: relative;
      content: '';
      width: 100%;
      padding-top: 100%;
      height: auto;
    }
    <!-- space slider html -->
    <div class="ez-slider">
      <figure>
        <a href="https://i.imgur.com/q5Y5RCH.png">
          <img src="https://i.imgur.com/q5Y5RCH.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/8HjXPXD.png">
          <img src="https://i.imgur.com/8HjXPXD.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/vUDcfcy.png">
          <img src="https://i.imgur.com/vUDcfcy.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/okTDHas.png">
          <img src="https://i.imgur.com/okTDHas.png" alt="" />
        </a>
      </figure>
    </div>
    
    <!-- ocean slider html -->
    <div class="ez-slider">
      <figure>
        <a href="https://i.imgur.com/x7ZYW4i.png">
          <img src="https://i.imgur.com/x7ZYW4i.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/EYTCssm.png">
          <img src="https://i.imgur.com/EYTCssm.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/3sAFPmL.png">
          <img src="https://i.imgur.com/3sAFPmL.png" alt="" />
        </a>
      </figure>
    </div>
    
    <!-- landscape slider html -->
    <div class="ez-slider">
      <figure>
        <a href="https://i.imgur.com/IqLrd0o.png">
          <img src="https://i.imgur.com/IqLrd0o.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/6JplNl6.png">
          <img src="https://i.imgur.com/6JplNl6.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/6X5GKWJ.png">
          <img src="https://i.imgur.com/6X5GKWJ.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/SefTwI1.png">
          <img src="https://i.imgur.com/SefTwI1.png" alt="" />
        </a>
      </figure>
      <figure>
        <a href="https://i.imgur.com/qBmDrTU.png">
          <img src="https://i.imgur.com/qBmDrTU.png" alt="" />
        </a>
      </figure>
    </div>
    
    <!-- Include Slick CSS library -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.css" rel="stylesheet"/>
    
    <!-- Include Slick Theme CSS library -->
    <link href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.min.css" rel="stylesheet"/>
    
    <!-- Include EasyZoom CSS library -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/css/easyzoom.css" rel="stylesheet"/>
    
    <!-- Include jQuery library -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    
    <!-- Include Slick JS library after jQuery -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.min.js"></script>
    
    <!-- Include EasyZoom JS library after jQuery -->
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/src/easyzoom.js"></script>
    
    <!-- Make sure you run all your custom JS after libraries -->