Before and After Image Comparison

Responsive before/after image comparison slider.

Display estimated delivery date
PHP
/**
 * Snippet Name:     Before and After Image Comparison
 * Snippet Author:   coding-bunny.com
 * Description:      Responsive before/after image comparison slider.
 * Version:          1.0.0
 * 
 * USAGE EXAMPLES:
 * [image_comparison_slider before="URL1" after="URL2" title_before="BEFORE" title_after="AFTER" show_titles="1"]
 */

add_action('wp_enqueue_scripts', function() {
    $css = <<<CSS
.ics-image-comparison-slider {
    position: relative;
    display: inline-block;
    max-width: 100%;
    user-select: none;
}
.ics-slider-wrapper {
    position: relative;
    width: 100%;
}
.ics-before-img,
.ics-after-img {
    display: block;
    max-width: 100%;
    width: 100%;
    height: auto;
    user-select: none;
    pointer-events: none;
    position: absolute;
    top: 0; left: 0;
}

.ics-slider-handle-line {
    position: absolute;
    top: 0; bottom: 0;
    left: 50%;
    width: 2px;
    background: #fff;
    transform: translateX(-50%);
    z-index: 40;
    pointer-events: none;
}
.ics-slider-handle {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 20px;
    height: 20px;
    background: #3857e9;
    border-radius: 50%;
    transform: translate(-50%, -50%);
    z-index: 50;
    cursor: ew-resize;
    display: flex;
    align-items: center;
    justify-content: center;
    box-shadow: 0 0 0 2px #fff, 0 0 4px #3857e9;
    transition: background 0.15s;
    outline: none;
    border: none;
}
.ics-slider-handle:after { display: none; }
.ics-slider-range {
    position: absolute;
    top: 0; left: 0;
    width: 100%; height: 100%;
    opacity: 0;
    z-index: 100;
    cursor: ew-resize;
}

.ics-img-titles-row {
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    width: 100%;
    z-index: 60;
    pointer-events: none;
    box-sizing: border-box;
    padding: 0 8px 8px 8px;
}
.ics-img-title {
    color: #fff;
    background: rgba(0,0,0,0.70);
    padding: 3px 10px;
    font-size: 12px;
    border-radius: 0;
    max-width: 48%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    text-shadow: 0 1px 4px #000;
    box-sizing: border-box;
    transition: opacity 0.18s;
}
.ics-img-title.ics-title-before {
    text-align: left;
    justify-self: flex-start;
}
.ics-img-title.ics-title-after {
    text-align: right;
    justify-self: flex-end;
}
CSS;
    wp_register_style('ics-inline-style', false);
    wp_enqueue_style('ics-inline-style');
    wp_add_inline_style('ics-inline-style', $css);

    $js = <<<JS
jQuery(document).ready(function($){
    function setupSlider(\$container){
        var \$wrapper = \$container.find('.ics-slider-wrapper'),
            \$beforeImg = \$wrapper.find('.ics-before-img'),
            \$afterImg = \$wrapper.find('.ics-after-img'),
            \$slider = \$wrapper.find('.ics-slider-range'),
            \$handle = \$wrapper.find('.ics-slider-handle'),
            \$line = \$wrapper.find('.ics-slider-handle-line'),
            \$titleBefore = \$wrapper.find('.ics-img-title.ics-title-before'),
            \$titleAfter = \$wrapper.find('.ics-img-title.ics-title-after');

        function setWrapperHeight(){
            var h = \$beforeImg.height();
            \$wrapper.height(h);
            \$afterImg.height(h);
        }

        function setSlider(val){
            var percent = val + '%';
            \$afterImg.css('clip-path', 'inset(0 0 0 ' + percent + ')');
            \$line.css('left', percent);
            \$handle.css('left', percent);

            var value = parseFloat(val);

            if(\$titleBefore.length){
                var beforeOpacity = (100 - value)/100;
                \$titleBefore.css('opacity', beforeOpacity < 0.03 ? 0 : beforeOpacity);
            }

            if(\$titleAfter.length){
                var afterOpacity = value/100;
                \$titleAfter.css('opacity', afterOpacity < 0.03 ? 0 : afterOpacity);
            }
        }

        var imagesLoaded = 0;
        function tryInit(){
            imagesLoaded++;
            if(imagesLoaded>=2) {
                setWrapperHeight();
                setSlider(\$slider.val());
            }
        }
        \$beforeImg.on('load', tryInit);
        \$afterImg.on('load', tryInit);
        if(\$beforeImg[0].complete) tryInit();
        if(\$afterImg[0].complete) tryInit();

        $(window).on('resize', setWrapperHeight);

        \$slider.on('input change', function(){ setSlider(this.value); });

        var dragging = false;
        \$handle.on('mousedown touchstart', function(e){
            dragging = true; e.preventDefault();
        });
        $(document).on('mouseup touchend', function(){ dragging = false; });
        $(document).on('mousemove touchmove', function(e){
            if(!dragging) return;
            var pageX = e.pageX || (e.originalEvent.touches && e.originalEvent.touches[0].pageX);
            var offset = \$wrapper.offset().left;
            var width = \$wrapper.width();
            var percent = ((pageX - offset) / width) * 100;
            percent = Math.max(0, Math.min(100, percent));
            \$slider.val(percent);
            setSlider(percent);
        });

        \$handle.on('keydown', function(e){
            var val = parseInt(\$slider.val(), 10);
            if(e.key === 'ArrowLeft' || e.key === 'ArrowDown') {
                \$slider.val(Math.max(val - 1, 0)).trigger('input');
            } else if(e.key === 'ArrowRight' || e.key === 'ArrowUp') {
                \$slider.val(Math.min(val + 1, 100)).trigger('input');
            }
        });
    }
    $('.ics-image-comparison-slider').each(function(){ setupSlider($(this)); });
});
JS;
    wp_register_script('ics-inline-script', false);
    wp_enqueue_script('ics-inline-script');
    wp_add_inline_script('ics-inline-script', $js);
});

add_shortcode('image_comparison_slider', function($atts) {
    $atts = shortcode_atts([
        'before' => '',
        'after'  => '',
        'title_before' => '',
        'title_after'  => '',
        'width' => '100%',
        'show_titles' => '0',
    ], $atts, 'image_comparison_slider');
    $before = esc_url($atts['before']);
    $after  = esc_url($atts['after']);
    $title_before = esc_html($atts['title_before']);
    $title_after  = esc_html($atts['title_after']);
    $width  = esc_attr($atts['width']);
    $show_titles = $atts['show_titles'] === '1' || strtolower($atts['show_titles']) === 'true';

    if (empty($before) || empty($after)) {
        return '<p><em>Both "before" and "after" URLs are required.</em></p>';
    }
    ob_start(); ?>
    <div class="ics-image-comparison-slider" style="width:<?php echo $width; ?>;">
        <div class="ics-slider-wrapper">
            <img src="<?php echo $before; ?>" alt="" class="ics-before-img" draggable="false" />
            <img src="<?php echo $after; ?>" alt="" class="ics-after-img" draggable="false" />
            <?php if($show_titles && ($title_before || $title_after)): ?>
                <div class="ics-img-titles-row">
                    <span class="ics-img-title ics-title-before"><?php echo $title_before; ?></span>
                    <span class="ics-img-title ics-title-after"><?php echo $title_after; ?></span>
                </div>
            <?php endif; ?>
            <div class="ics-slider-handle-line"></div>
            <input type="range" min="0" max="100" value="50" class="ics-slider-range" aria-label="Image comparison slider" />
            <span class="ics-slider-handle" tabindex="0" aria-label="Drag to compare images"></span>
        </div>
    </div>
    <?php
    return ob_get_clean();
});

How To Implement This Solution?

Leave a Reply