DEV Community

Cover image for If you know jQuery Focuspoint you will like Vanilla Focus
Simon Köhler
Simon Köhler

Posted on

1 1

If you know jQuery Focuspoint you will like Vanilla Focus

I liked using the jQuery Focuspoint plugin when I was still using jQuery. But since I've been working only with pure JS (again) for about 2 years now, I just rewrote the plugin and made it independent from jQuery.

The JavaScript Code

Here's the full JavaScript code of the plugin version 1.0.0. You can find the latest version here on GitHub.

/**
 * vanillafocus; version: 1.0.0
 * Author: https://simon-koehler.com/en
 * Source: https://github.com/koehlersimon/vanillafocus
 * Copyright (c) 2020 S.Köhler; MIT License
 */
( function( root, factory ) {

    var pln = 'vanillafocus';

    if (typeof define === 'function' && define.amd) {
        define([],factory(pln));
    }
    else if (typeof exports === 'object' ) {
        module.exports = factory(pln);
    }
    else {
        root[pln] = factory(pln);
    }

}( this, function(pln) {

    'use strict';

    var defaults = {
        reCalcOnWindowResize: true,
        throttleDuration: 17
    };

    /**
     * @param {Object} defaults
     * @param {Object} options
     */
    var extend = function( target, options ) {
        var prop, extended = {};
        for ( prop in defaults ) {
            if ( Object.prototype.hasOwnProperty.call( defaults, prop ) ) {
                extended[ prop ] = defaults[ prop ];
            }
        }
        for ( prop in options ) {
            if ( Object.prototype.hasOwnProperty.call( options, prop ) ) {
                extended[ prop ] = options[ prop ];
            }
        }
        return extended;
    };

    /**
     * @param {Object} options
     * @constructor
     */
    function Plugin( options ) {
        this.options = extend( defaults, options );
        this.init();
    }

    /**
     * @public
     * @constructor
     */
    Plugin.prototype = {
        init: function() {
            var selectors = document.querySelectorAll( this.options.selector );
            selectors.forEach((el, i) => {
                var fp = focusPoint(el,this.options);
                if (this.options.reCalcOnWindowResize) fp.windowOn();
            });
        },
        adjustFocus: function() {
            var selectors = document.querySelectorAll( this.options.selector );
            selectors.forEach((el, i) => {
                adjustFocus(el);
            });
        }
    };

    var setupContainer = function(focusElement) {
        let imageSrc = focusElement.querySelector('img').getAttribute('src');
        focusElement.setAttribute('data-image-src', imageSrc);
        resolveImageSize(imageSrc, function(err, dim) {
        focusElement.setAttribute('data-image-w', dim.width);
        focusElement.setAttribute('data-image-h', dim.height);
            adjustFocus(focusElement);
        });
    };

    var resolveImageSize = function(src, cb) {
        let image = document.createElement('img');
        image.src = src;
        image.addEventListener('load',function(e){
            cb(null, {
                width: e.target.width,
                height: e.target.height
            });
        });
    };

    var throttle = function(fn, ms) {
        var isRunning = false;
        return function() {
            var args = Array.prototype.slice.call(arguments, 0);
            if (isRunning) return false;
            isRunning = true;
            setTimeout(function() {
                isRunning = false;
                fn.apply(null, args);
            }, ms);
        };
    };

    var calcShift = function(conToImageRatio, size, imageSize, focusSize, toMinus) {
        var center = Math.floor(size / 2);
        var focusFactor = (focusSize + 1) / 2;
        var scaledImage = Math.floor(imageSize / conToImageRatio);
        var focus =  Math.floor(focusFactor * scaledImage);
        if (toMinus) focus = scaledImage - focus;
        var focusOffset = focus - center;
        var remainder = scaledImage - focus;
        var containerRemainder = size - center;
        if (remainder < containerRemainder) focusOffset -= containerRemainder - remainder;
        if (focusOffset < 0) focusOffset = 0;
        return (focusOffset * -100 / size)  + '%';
    };

    var adjustFocus = function(focusElement) {
        var imageW = focusElement.getAttribute('data-image-w');
        var imageH = focusElement.getAttribute('data-image-h');
        var imageSrc = focusElement.getAttribute('data-image-src');

        if (!imageW || !imageH || !imageSrc) {
            return setupContainer(focusElement);
        }

        var containerW = focusElement.offsetWidth;
        var containerH = focusElement.offsetHeight;
        var focusX = parseFloat(focusElement.getAttribute('data-focus-x'));
        var focusY = parseFloat(focusElement.getAttribute('data-focus-y'));
        var image = focusElement.querySelector('img');

        var hShift = 0;
        var vShift = 0;

        if (!(containerW > 0 && containerH > 0 && imageW > 0 && imageH > 0)) {
            return false;
        }

        var wR = imageW / containerW;
        var hR = imageH / containerH;

        image.style.maxWidth = '';
        image.style.maxHeight = '';

        if (imageW > containerW && imageH > containerH) {
        if((wR > hR)){
            image.style.maxHeight = '100%';
        }
        else{
            image.style.maxWidth = '100%';
        }
        }

        if (wR > hR) {
            hShift = calcShift(hR, containerW, imageW, focusX);
        } else if (wR < hR) {
            vShift = calcShift(wR, containerH, imageH, focusY, true);
        }

        image.style.top = vShift;
        image.style.left = hShift;

    };

    var focusPoint = function(el, options) {
        var thrAdjustFocus = options.throttleDuration ? throttle(function(){adjustFocus(el);}, options.throttleDuration) : function(){adjustFocus(el);};
        var isListening = false;
        adjustFocus(el);
        return {
            adjustFocus: function() {
                return adjustFocus(el);
            },
            windowOn: function() {
                if (isListening) return;
                window.addEventListener('resize',thrAdjustFocus);
                return isListening = true;
            },
            windowOff: function() {
                if (!isListening) return;
                window.removeEventListener('resize',thrAdjustFocus);
                isListening = false;
                return true;
            }
        };
    };

    return Plugin;

} ) );
Enter fullscreen mode Exit fullscreen mode

HTML markup

<div class="vf-ratio vf-ratio-16x9">
    <div class="vf-container" 
    data-focus-x="0.66"
    data-focus-y="0.36"
    data-image-w="750"
    data-image-h="498">
        <img src="https://i.imgur.com/PA09XUX.jpeg" alt="Demo">
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

JavaScript for Page Header or Footer

var myImages = new vanillafocus({
     selector: ".vf-container",
     reCalcOnWindowResize: true
})
Enter fullscreen mode Exit fullscreen mode

Official Website:

https://vanillafocus.simon-koehler.com/

My Website:

https://simon-koehler.com/en

Do your career a big favor. Join DEV. (The website you're on right now)

It takes one minute, it's free, and is worth it for your career.

Get started

Community matters

Top comments (0)

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay