Search code examples
javascriptajaxonmouseover

Looping over AJAX requested images on mouseover, stopping the loop on mouseleave


I've got a grid of models, each consisting of a link, image and name. On mouseenter, I'm looking to replace the src-attribute of the hovered image every second by a list of AJAX loaded images in a loop. On mouseleave, the original image should revert and the loop should stop.

<div class="c-grid c-grid--models">
    <a href="#link-to-model" class="c-model" data-id="1">
        <figure class="c-model__wrap">
            <img src="http://placeholder.image" data-original="http://lazyloaded.image" class="c-model__image" />
            <figcaption class="c-model__name">Model 1</figcaption>
        </figure>
    </a>
    <a href="#link-to-model" class="c-model" data-id="2">
        <figure class="c-model__wrap">
            <img src="http://placeholder.image" data-original="http://lazyloaded.image" class="c-model__image" />
            <figcaption class="c-model__name">Model 2</figcaption>
        </figure>
    </a>
</div>

The images are lazyloaded when they appear in the viewport. They are stored in the data-original tag. Below is the script that I got working.

    var _brakes = 1;
    var _timer1 = null;
    var _timer2 = null;
    var _data = null;
    $('.c-grid--models .c-model').hover(
        function() {
            _brakes = 0;
            _id     = $(this).attr('data-id');
            $image = $('img',$(this));
            var $this = $(this);
            _timer1 = setTimeout(function (){
                $this.request('onHover', {
                    data : {
                        id : _id
                    },
                    success: function(data) {
                        _data = $.parseJSON(data.result);
                        $.each(_data, function(i, new_image) {
                            _timer2 = setTimeout(function (){
                                if(_brakes == 0) {
                                    $image.attr('src', new_image);
                                } else {
                                    clearTimeout(_timer1);
                                    clearTimeout(_timer2);
                                    _data = null;
                                    _timer1 = null;
                                    _timer2 = null;
                                }
                            }, i * 1000);
                        });
                    }
                });
            }, 1000);
        }, function() {
            _brakes = 1;
            $image = $('img',$(this));
            $image.attr('src',$image.attr('data-original'));
            clearTimeout(_timer1);
            clearTimeout(_timer2);
            _data = null;
            _timer1 = null;
            _timer2 = null;
        }
    );

Problems:

  1. The loop stops when the last image has been reached. I would like to keep looping untill the mouseleave event is fired
  2. Upon mouseleave and mouse(re)enter, the imagerotation speeds up and behaves erraticly. It seems the first loop hasn't properly stopped.
  3. The images of one model get crossed with those of another model when hovering back and forth.

Note: $(this).request('onHover'... is part of the October CMS Ajax framework. It executes a PHP function that returns a JSON array of all the images of the model that currently is hovered.

Can anyone help with this issue? It might be that the logic I used is not right. Any input would be greatly appreciated. Thanks!

Related:

Stopping infinite loop on mouseleave or mouseout


Solution

  • A good friend helped me out and solved the problem for me.

    We took a different approach, storing the total amount of images and the current image in data-attributes on each html element.

    Instead of returning the whole array of images, we now rotate over the images one per one.

    New HTML:

    <div class="c-grid c-grid--models">
        <a href="#link-to-model" class="c-model" data-id="1" data-image-counter="5" data-current-image="0">
            <figure class="c-model__wrap">
                <img src="http://placeholder.image" data-original="http://lazyloaded.image" class="c-model__image" />
                <figcaption class="c-model__name">Model 1</figcaption>
            </figure>
        </a>
        <a href="#link-to-model" class="c-model" data-id="2" data-image-counter="5" data-current-image="0">
            <figure class="c-model__wrap">
                <img src="http://placeholder.image" data-original="http://lazyloaded.image" class="c-model__image" />
                <figcaption class="c-model__name">Model 2</figcaption>
            </figure>
        </a>
    </div>
    

    New JavaScript:

    $(function() {
    
        var hoveredItem = null;
    
        // Don't fire on touch devices
        if($('.c-grid--models').length && !Modernizr.touchevents) {
    
            $('.c-grid--models .c-model').hover(
                function() { // mouseenter
    
                    $(this).data("mouse-hover", true);
    
                    if(!$(this).data("is-busy") ) {
                        $(this).data("is-busy", true);
                        GetNextImage($(this));
                    }
                    // get next image with delay
                }, function() { // mouseleave
                    // set original image and stop loop
                    $(this).data("mouse-hover", false);
                    var orignalimage = $("img",$(this)).data("original");
                    $("img", $(this)).attr("src", orignalimage);
                }
            );
        }
    
        function GetNextImage(imageContainer) {
            hoveredItem = imageContainer;
    
            if (parseInt(imageContainer.data("image-counter")) <= 1) return;
    
            setTimeout( function() {
    
                if (imageContainer.data("mouse-hover") == true)
                {
    
                    imageContainer.data("current-image", parseInt(imageContainer.data("current-image")) + 1);
    
                    $(this).request("onHover", {
                        data: {
                            id: imageContainer.data("id"),
                            image_id: imageContainer.data("current-image")
                        }, success: function (data) {
    
                            // Reset
                            var total = parseInt(imageContainer.data("image-counter"))-1;
                            var current = parseInt(imageContainer.data("current-image"));
                            if (current >= total) imageContainer.data("current-image", 0);
    
                            // async callback finished. no longer busy
                            if (imageContainer.data("mouse-hover") == true) {
                                var _data = $.parseJSON(data.result);
                                $('img',imageContainer).attr('src', _data);
    
                                GetNextImage(imageContainer); //Loop
                            } else {
                                imageContainer.data("is-busy", false);
                            }
                        }
                    });
                } else {
                    imageContainer.data("is-busy", false);
                }
            }, 1000);
        }
    
        $(window).scroll(function() {
            if (hoveredItem != null) {
                hoveredItem.data("mouse-hover", false);
    
            }
        });
    });
    

    Important to note is that on scrolling does not always fire onmouseleave. So upon scrolling we cancel the image rotation until the user mouseenters once more.

    I hope this can be useful. If somethings are not clear, just comment below and I'll update the answer.