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

Sentry blog image

How I fixed 20 seconds of lag for every user in just 20 minutes.

Our AI agent was running 10-20 seconds slower than it should, impacting both our own developers and our early adopters. See how I used Sentry Profiling to fix it in record time.

Read more

Top comments (0)

Image of Datadog

The Essential Toolkit for Front-end Developers

Take a user-centric approach to front-end monitoring that evolves alongside increasingly complex frameworks and single-page applications.

Get The Kit

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay