YouTube Animated Thumbnails & Preview Videos

On hover, each thumbnail cycles though its 3 thumbnails. Additional feature to view those 3 thumbnails all at once (non-animated)

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name          YouTube Animated Thumbnails & Preview Videos
// @namespace     http://userscripts.org/users/23652
// @description   On hover, each thumbnail cycles though its 3 thumbnails. Additional feature to view those 3 thumbnails all at once (non-animated)
// @include       http://*.youtube.com/*
// @include       http://youtube.com/*
// @include       https://*.youtube.com/*
// @include       https://youtube.com/*
// @exclude       http://*youtube.com/my_videos_edit*
// @exclude       http://*youtube.com/my_subscribers*
// @exclude       https://*youtube.com/my_videos_edit*
// @exclude       https://*youtube.com/my_subscribers*
// @copyright     JoeSimmons
// @version       1.1.0
// @license       GPL version 3 or any later version; http://www.gnu.org/copyleft/gpl.html
// @require       https://greasyfork.org/scripts/1884-gm-config/code/GM_config.js?version=4836
// @require       https://greasyfork.org/scripts/1885-joesimmons-library/code/JoeSimmons'%20Library.js?version=4838
// @require       https://greasyfork.org/scripts/2817-jsl-ajax-plugin/code/JSL%20-%20AJAX%20plugin.js?version=7911
// @grant         GM_getValue
// @grant         GM_registerMenuCommand
// @grant         GM_setValue
// @grant         GM_xmlhttpRequest
// ==/UserScript==


/* CHANGELOG -------------------------------------------------------------------------------------------

1.1.0 (4/8/2014)
    - fixed thumbnail previews sometimes not displaying the right video's thumbnail

1.0.94 (2/25/2014)
    - fixed problem with thumbnails appearing under the masthead

1.0.93 (11/18/2013)
    - removed fading, too inconsistent & laggy

1.0.92 (10/8/2013)
    - re-write of the entire script for readability and efficiency
    - added some minor fade effects
    - instead of making the video black when hovering over a thumbnail,
        the script will now keep showing the video
    - previews open in the opposite corner of where your mouse is
    - more reliable HQ image getting
    - only the hovered over thumbnail is animated, not all the images now

1.0.91
    - Added a new option that allows animated thumbnails only on hovered images

1.0.90
    - Moved the options button to inside the YouTube dropdown menu

1.0.89
    - Added compatibility for Opera & Chrome

------------------------------------------------------------------------------------------------------ */


(function () {
    'use strict';

    var rVi = /[a-z0-9-]+\.ytimg\.com\/vi\/([^\/]+)\//i,
        rYtimgUrl = /[a-z0-9-]+\.ytimg\.com\/vi\//i,
        rWhichImage = /(vi\/[a-z0-9-_]+\/)(1|3|mqdefault)(\.jpg)/i,
        x = 0, y = 0,
        isHoverEnabled, thumbSize, isAnimationEnabled, animationSpeed,
        menulist, __preview_timeout, __animation_interval, stillOnThumb;

    var preview = {
        // clears the timeout and hides the hover preview
        hide : function () {
            window.clearInterval(__preview_timeout);
            JSL('#hover_img').hide().find('img').attribute('src', '');
        },
        show : function () {
            // sets the timeout to show the hover preview
            __preview_timeout = window.setTimeout(function () {
                JSL('#hover_img').show('block');
            }, 400);
        }
    };

    function getNextImage(currentImage) {
        var imagesInOrder = ['1', 'mqdefault', '3'],
            indexOfNextImage = imagesInOrder.indexOf(currentImage) + 1;

        if ( indexOfNextImage > (imagesInOrder.length - 1) ) {
            // if it's on the last one, go back to the first
            return imagesInOrder[0];
        } else {
            // if it's not on the last one, show the next one
            return imagesInOrder[indexOfNextImage];
        }
    }

    function handleHoverLogic(urlIdPrefix) {
        var box = JSL('#hover_img'),
            hori, vert;

        preview.hide();

        // set the previews for this thumbnail
        JSL('#hover_img_1').attribute('src', urlIdPrefix + '1.jpg');
        JSL('#hover_img_2').attribute('src', urlIdPrefix + 'mqdefault.jpg');
        JSL('#hover_img_3').attribute('src', urlIdPrefix + '3.jpg');

        // check for a higher quality preview while we
        // wait for the image to display
        JSL.ajax([
            urlIdPrefix + 'maxresdefault.jpg',
            urlIdPrefix + 'hqdefault.jpg'
        ], {
            method : 'HEAD',
            onload : function (resp) {
                var img2 = JSL('#hover_img_2');

                // check for a 200 (OK) status
                // & that we're showing the correct thumbnail
                if ( resp.status === 200 && resp.url.getMatch(rVi, 1) === img2.attribute('src').getMatch(rVi, 1) ) {
                    img2.attribute('src', resp.url);
                    JSL.ajaxClear();
                }
            }
        });

        vert = !(y < (window.innerHeight / 2) ) ? 'top' : 'bottom'; // should the preview be on the top or bottom
        hori = !(x < ( (window.innerWidth - 15) / 2) ) ? 'left' : 'right'; // should the preview be on the left or right

        // set the vertical align style property of each of the 3 preview images
        JSL('#hover_img img').css('vertical-align', vert);

        // reset the position of the hover box
        box.css('top', 'auto').css('right', 'auto').css('bottom', 'auto').css('left', 'auto');

        // set the corner it will appear in
        box.css(vert, '0').css(hori, '0');

        // set a delay for the previews to show
        preview.show();
    }

    function handleAnimationLogic(elem) {
        __animation_interval = JSL.setInterval(function () {
            var currentImage = elem.attribute('src').getMatch(rWhichImage, 2),
                nextImage = getNextImage(currentImage);

            elem.attribute( 'src', elem.attribute('src').replace(rWhichImage, '$1' + nextImage + '$3') );
        }, animationSpeed);
    }

    function show(event) {
        var elem = JSL(event.target),
            id = elem.attribute('src').getMatch(rVi, 1),
            urlIdPrefix = 'http://' + elem.attribute('src').getMatch(rYtimgUrl) + id + '/';

        // filter out non-thumbnails
        if ( elem.is('img') && elem.isnt('#hover_img, img[id^="hover_img_"]') && urlIdPrefix.getMatch(rVi) ) {
            if (isHoverEnabled) {
                handleHoverLogic(urlIdPrefix);
            }
            
            if (isAnimationEnabled) {
                handleAnimationLogic(elem);
            }
        }
    }

    function hide(event) {
        var elem = JSL(event.target),
            id = elem.attribute('src').getMatch(rVi, 1),
            urlIdPrefix = 'http://' + elem.attribute('src').getMatch(rYtimgUrl) + id + '/';

        // clear the last HQ thumbnail request
        JSL.ajaxClear();

        // don't hide the preview if the mouse goes over it
        if ( elem.isnt('#hover_img, img[id^="hover_img_"]') ) {
            preview.hide();
        }

        // stop animating the current thumbnail
        if ( elem.is('img') && elem.isnt('#hover_img, img[id^="hover_img_"]') && elem.attribute('src').getMatch(rWhichImage) ) {
            JSL.clearInterval(__animation_interval); // stop the animation
            elem.attribute( 'src', elem.attribute('src').replace(rWhichImage, '$1mqdefault$3') ); // reset the thumbnail
        }
    }

    function trackMouse(event) {
        x = event.pageX - window.pageXOffset;
        y = event.pageY - window.pageYOffset;
    }

    function GM_config_open() {
        GM_config.open();
    }

    // Make sure the page is not in a frame
    if (window.self !== window.top) { return; }

    // String.prototype.getMatch by JoeSimmons
    // e.g., 'foobar'.getMatch(/foo(bar)/, 1) ==> 'bar'
    Object.defineProperty(String.prototype, 'getMatch', {
        value : function (regex, index) {
            var match = this.match(regex) || ['', '', '', '', '', '', '', '', '', ''];

            if (typeof index === 'number' && index > -1) {
                return match[index];
            }

            return match[0];
        }
    });

    GM_config.init('YouTube Animated Thumbnails Options', {
        hoverimages : {
            section : ['Hover Options'],
            label : 'Enable Hover Images?',
            type : 'checkbox',
            'default' : true,
            title : 'Hovering over a thumbnail shows 3 preview images.'
        },
        thumbSize : {
            label : 'Hover Thumbnail Size',
            type : 'select',
            options : {
                '90' : 'Small',
                '216' : 'Medium',
                '432' : 'Large'
            },
            'default' : '432',
            'title' : 'Choose the size of the hovering thumbnails'
        },
        animatedthumbnails : {
            section : ['Animated Thumbs Options'],
            label : 'Enable Animated Thumbnails?',
            type : 'checkbox',
            'default' : true,
            title : 'Thumbnails cycle through the 3 images while hovering.'
        },
        animationspeed : {
            label : 'Animation (Cycle) Speed',
            type : 'select',
            options : {
                '1200' : 'Super Slow',
                '800' : 'Slow',
                '600' : 'Medium',
                '400' : 'Fast',
                '200' : 'Super Fast'
            },
            'default' : '600',
            title : 'Set the speed of the cycled images'
        }
    });

    JSL.runAt('interactive', function () {
        isHoverEnabled = GM_config.get('hoverimages') === true;
        thumbSize = GM_config.get('thumbSize');
        isAnimationEnabled = GM_config.get('animatedthumbnails') === true;
        animationSpeed = parseInt(GM_config.get('animationspeed'), 10);

        JSL.addStyle('' +
            '#hover_img { ' +
                'position: fixed; ' +
                'z-index: 999999999999; ' +
                'background: #000000; ' +
                'border: 1px solid #000000; ' +
                'outline: 1px solid #CCCCCC; ' +
            '}' +
            '#hover_img img { ' +
                'max-height: ' + thumbSize + 'px; ' +
            '}' +

            // fix for images showing under the youtube player
            '#hover_img img { ' +
                'background-color: #000000; ' +
            '}' +
            '#yt_at_ops { ' +
                'display: inline-block; ' +
                'padding: 6px; ' +
            '}' +
            '#GM_config { ' +
                'z-index: 999999999999 !important; ' + // 999,999,999,999
            '}' +
        '');

        JSL(document.body).append('' +
            '<div id="hover_img" style="display: none;">' +
                '<img src="" alt="" id="hover_img_1" />' +
                '<img src="" alt="" id="hover_img_2" />' +
                '<img src="" alt="" id="hover_img_3" />' +
            '</div>' +
        '');
        
        // Add a user script command for the options menu
        if (typeof GM_registerMenuCommand === 'function') {
            GM_registerMenuCommand('YouTube Animated Thumbnails Options', GM_config.open);
        }

        // Add an options button to the page
        JSL('#masthead-expanded-menu-list, #footer-main').append('' +
            '<li id="yt_at_ops" class="masthead-expanded-menu-item">' +
                '<a href="javascript: void(0);" class="yt-uix-sessionlink">Animated Thumbnails Options</a>' +
            '</li>' +
        '');
        JSL('#yt_at_ops a').addEvent('click', GM_config.open);

        if (isHoverEnabled) {
            JSL.addEvent(window, 'mousemove', trackMouse);
            JSL.addEvent(window, 'click', preview.hide);
        }
        JSL.addEvent(window, 'mouseover', show);
        JSL.addEvent(window, 'mouseout', hide);
    });

})();