Map margins manager

Open in CodeSandbox

Manager for calculating optimal margins from the edges of the map container.

Each map has its own margins manager. To add an area over the map to the manager, use the addArea method. The addArea method returns an object (accessor) that represents the occupied rectangular area. The accessor allows you to change or remove an area from the object manager.

The area is described as an object containing information about offsets from the edge of the map and the size of this area. Either pixels (px) or percentages (%) can be used as units of measurement. Percentages are calculated relative to the size of the map container.

Controls support the adjustMapMargin option, which takes a Boolean value. When set to true, the control registers its dimensions in the map's margins manager.

multiRouter.MultiRoute and Clusterer support the useMapMargin option, which allows using the map margins.
Many of the map methods support this option, such as setBounds, panTo, and setCenter.

<!DOCTYPE html>
<html lang="en">
    <head>
        <title>Examples. Map margins manager.</title>
        <meta charset="UTF-8" />
        <!--
        Set your own API-key. Testing key is not valid for other web-sites and services.
        Get your API-key on the Developer Dashboard: https://developer.tech.yandex.ru/keys/
    -->
        <script src="https://api-maps.yandex.ru/2.1/?lang=en_RU&amp;apikey=<your API-key>"></script>
        <script src="visualizeArea.js"></script>
        <script src="margin_manager.js"></script>

        <style>
            * {
                margin: 0;
                padding: 0;
                list-style: none;
            }
            html,
            body,
            .map,
            .viewport {
                width: 100%;
                height: 100%;
                margin: 0;
                padding: 0;
            }
            .viewport {
                position: relative;
            }
            .rect {
                position: absolute;
                background-color: rgba(200, 200, 200, 0.45);
                border: 2px dashed #555;
                box-sizing: border-box;
            }
            .area-holder {
                position: absolute;
                left: 0;
                top: 0;
                width: 100%;
                height: 100%;
                pointer-events: none;
            }
            .area-holder.is-hidden {
                display: none;
            }
            .map-bounds {
                position: absolute;
                left: 0;
                top: 0;
                right: 0;
                bottom: 0;
                box-sizing: border-box;
                border: 0 solid rgba(34, 148, 230, 0.2);
                pointer-events: none;
            }
            .is-hidden {
                display: none;
            }
            button {
                margin-right: 5px;
                padding: 5px;
                cursor: pointer;
            }
        </style>
    </head>
    <body>
        <div class="viewport">
            <div id="map" class="map"></div>
            <div class="map-bounds is-hidden"></div>
        </div>
    </body>
</html>
ymaps.ready(["util.dom.className"], function () {
    var balloonPosition = [55.83866, 37.712326], // The position of the balloon.
        Layout = ymaps.templateLayoutFactory.createClass(
            [
                "Centering<br>",
                '<button type="button" class="no-margin">without margings</button>',
                '<button type="button" class="with-margin">considering margins</button>',
            ].join(""),
            {
                build: function () {
                    Layout.superclass.build.call(this, arguments);
                    var container = this.getElement();
                    container.addEventListener("click", function (event) {
                        var target = event.target;
                        if (target.tagName.toLowerCase() == "button") {
                            map.panTo(balloonPosition, {
                                useMapMargin:
                                    target.className.match(/with-margin/i),
                            });
                        }
                    });
                },
            }
        ),
        map = new ymaps.Map(
            "map",
            {
                center: [55.85, 37.7124],
                zoom: 11,
                controls: [],
            },
            {
                balloonContentLayout: Layout,
                balloonAutoPan: false,
                balloonPanelMaxMapArea: 0,
                balloonCloseButton: false,
            }
        );

    /**
     * For elements on the page, we specify the area occupied over the map (the position and size).
     *  Values are supported in pixels (px) and percentages (%).
     *  If the unit of measurement is omitted, pixels are assumed.
     */
    var mapAreas = [
        // Panel on the left.
        {
            top: 0,
            left: 0,
            width: "80px",
            height: "100%", // The percentages are calculated relative to the size of the map container.
        },
        // Block in the right corner.
        {
            top: 10,
            right: 10,
            width: "40%",
            height: "40%",
        },
    ];
    // Adding each block to the margins manager.
    mapAreas.forEach(function (area) {
        // The 'addArea' method of the margins manager returns an object (the accessor), which provides access to the rectangular area in the margins manager.
        var accessor = map.margin.addArea(area);
        /**
         * If we call the 'remove' method for the accessor, the area will be removed from the margins manager.
         *  Example: accessor.remove()
         */

        visualizeArea(accessor);
    });

    map.balloon.open(balloonPosition);

    /**
     * The controls support the 'adjustMapMargin' option.
     *  When set to true, the control automatically adds its dimensions to the margins manager.
     */
    var toggleAreaBtn = new ymaps.control.Button({
        data: {
            content: "Show occupied areas",
            title: "Show all occupied areas from the margins manager",
        },
        options: {
            /**
             * adjustMapMargin: true,
             *  Maximum button width.
             */
            maxWidth: 300,
        },
    });
    /**
     * A click on the map displays all the areas added to
     *  the margins manager.
     */
    toggleAreaBtn.events.add(["select", "deselect"], function (event) {
        var container = document.getElementsByClassName("area-holder")[0],
            mode = event.originalEvent.type == "select" ? "remove" : "add";

        if (container) {
            ymaps.util.dom.className[mode](container, "is-hidden");
        }
    });
    map.controls.add(toggleAreaBtn);

    var toggleMarginBtn = new ymaps.control.Button({
        data: { content: "Show margins", title: "Show map margins" },
        options: {
            /**
             * Allowing the control to automatically add its own dimensions to the margins manager.
             *  In order for the control to register itself in the margins manager, uncomment this line.
             *  adjustMapMargin: true,
             */
            maxWidth: 200,
        },
    });
    toggleMarginBtn.events.add(["select", "deselect"], function (event) {
        var container = document.getElementsByClassName("map-bounds")[0],
            mode = event.originalEvent.type == "select" ? "remove" : "add";

        if (container) {
            ymaps.util.dom.className[mode](container, "is-hidden");
        }
    });
    map.controls.add(toggleMarginBtn);

    // Showing map margins.
    function updateMapMargins() {
        var margin = map.margin.getMargin();
        document.getElementsByClassName("map-bounds")[0].style.borderWidth =
            margin.join("px ") + "px";
    }
    updateMapMargins();
    map.events.add("marginchange", updateMapMargins);
});
/**
 * @fileOverview
 * Auxiliary functions for the example.
 *
 */
(function () {
    var container;

    /**
     * Visual representation of the occupied area.
     * Adding an element to the DOM tree to represent the occupied area.
     * @param {Object} accessor Instance of map.margin.Accessor
     */
    window.visualizeArea = function visualizeArea(accessor) {
        if (!container) {
            container = document.createElement("div");
            container.className = "area-holder is-hidden";
            document.body.appendChild(container);
        }

        var markElement = document.createElement("div");
        markElement.className = "rect";

        // Requesting a description of the rectangular area from the accessor and setting the style of the DOM element based on it.
        updateElementStyles(markElement, accessor.getArea());

        container.appendChild(markElement);

        var eventsGroup = accessor.events.group();

        eventsGroup.add("change", function () {
            updateElementStyles(markElement, accessor.getArea());
        });

        accessor.events.once("remove", function () {
            eventsGroup.removeAll();
            container.removeChild(markElement);
            markElement = null;
        });
    };

    function updateElementStyles(element, area) {
        element.style.cssText = "";
        for (var key in area) {
            if (area.hasOwnProperty(key)) {
                var value = String(area[key]);
                if (!isNaN(Number(value[value.length - 1]))) {
                    value += "px";
                }
                element.style[key] = value;
            }
        }
    }
})();