---
metadata:
  - name: generator
    content: Diplodoc Platform v5.39.1
alternate:
  - https://yandex.com/dev/jsapi-v2-1/doc/en/v2-1/examples/cases/balloon_autopan.md
  - https://yandex.com/dev/jsapi-v2-1/doc/ru/v2-1/examples/cases/balloon_autopan.md
---
> **Documentation Index:** Fetch the complete configuration index at https://yandex.com/dev/jsapi-v2-1/doc/en/llms.txt

# Custom balloon layout with autopositioning

<iframe id="LIVE-EXAMPLE" style="width:100%;height: 400px;border-radius: 8px;border: 1px solid rgba(92, 94, 102, 0.14);" frameBorder="0" src="https://yastatic.net/s3/front-maps-static/maps-front-jsapi-v2-1/examples/2/out/s3-cases/en/balloon_autopan/index.html" allow="fullscreen"></iframe>

<a target="_blank" rel="noopener noreferrer" style="display: inline-block;margin-top: 10px;cursor: pointer;border-radius: 4px;padding: 9px 12px;background: #151515;color: white;text-decoration: none;font-weight: 500" href="https://codesandbox.io/p/sandbox/qhfsz6?file=index.html">Open in CodeSandbox</a>

<div id="references">
    <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/ready">ready</a>,
    <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/Map">Map</a>,
    <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/map.Balloon">map.Balloon</a>,
    <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/templateLayoutFactory">templateLayoutFactory</a>,
    <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/IBalloon">IBalloon</a>
</div>
<p>
 To create your own balloon "from scratch", you can use the <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/templateLayoutFactory">templateLayoutFactory</a> factory. However,
 the balloon class must implement the <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/IBalloon">IBalloon</a> interface.
</p>
<p>
 In order for the balloon to support autopositioning, meaning it automatically adjusts to the size of the content, you need to
 correctly state its current size.
 To do this, implement the <a href="https://yandex.ru/dev/jsapi-v2-1/doc/en/v2-1/ref/reference/ILayout#getShape">getShape()</a> method.
</p>
<p>
  When you change the content of a balloon in the onSublayoutSizeChange() method, override the offset option and throw the shapechange event.
</p>

{% list tabs %}

-   index.html

    ```html
    <!DOCTYPE html>
    <html>
        <head>
            <title>Examples. Custom balloon layout with autopositioning.</title>
            <meta
                http-equiv="Content-Type"
                content="text/html; charset=UTF-8"
            />
            <link
                href="https://yandex.st/bootstrap/2.3.2/css/bootstrap.min.css"
                rel="stylesheet"
            />
            
            <!--
            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/
        -->
            
            
            
        </head>
        <body>
            <div id="map"></div>
            <button id="set-balloon-header" class="btn">
                Set the title of a balloon
            </button>
            <button id="set-balloon-content" class="btn">
                Set the contents of a balloon
            </button>
        </body>
    </html>
    ```

-   balloon_autopan.js

    ```js
    ymaps.ready(function () {
        // Creating an instance of the map and binding it to the created container.
        var myMap = new ymaps.Map(
                "map",
                {
                    center: [55.751574, 37.573856],
                    zoom: 9,
                    behaviors: ["default", "scrollZoom"],
                },
                {
                    searchControlProvider: "yandex#search",
                }
            ),
            // Creating a balloon layout based on Twitter Bootstrap.
            MyBalloonLayout = ymaps.templateLayoutFactory.createClass(
                '<div class="popover top">' +
                    '<a class="close" href="#">&times;</a>' +
                    '<div class="arrow"></div>' +
                    '<div class="popover-inner">' +
                    "$[[options.contentLayout observeSize minWidth=235 maxWidth=235 maxHeight=350]]" +
                    "</div>" +
                    "</div>",
                {
                    /**
                     * Builds an instance of a layout based on a template and adds it to the parent HTML element.
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#build
                     * @function
                     * @name build
                     */
                    build: function () {
                        this.constructor.superclass.build.call(this);

                        this._$element = $(".popover", this.getParentElement());

                        this.applyElementOffset();

                        this._$element
                            .find(".close")
                            .on("click", $.proxy(this.onCloseClick, this));
                    },

                    /**
                     * Removes the layout contents from DOM.
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/layout.templateBased.Base.xml#clear
                     * @function
                     * @name clear
                     */
                    clear: function () {
                        this._$element.find(".close").off("click");

                        this.constructor.superclass.clear.call(this);
                    },

                    /**
                     * The method will be invoked by the API's template system when resizing the nested layout.
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
                     * @function
                     * @name onSublayoutSizeChange
                     */
                    onSublayoutSizeChange: function () {
                        MyBalloonLayout.superclass.onSublayoutSizeChange.apply(
                            this,
                            arguments
                        );

                        if (!this._isElement(this._$element)) {
                            return;
                        }

                        this.applyElementOffset();

                        this.events.fire("shapechange");
                    },

                    /**
                     * Moving the balloon so the "tail" points at the anchor point.
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
                     * @function
                     * @name applyElementOffset
                     */
                    applyElementOffset: function () {
                        this._$element.css({
                            left: -(this._$element[0].offsetWidth / 2),
                            top: -(
                                this._$element[0].offsetHeight +
                                this._$element.find(".arrow")[0].offsetHeight
                            ),
                        });
                    },

                    /**
                     * Closes the balloon when the "x" is clicked, throwing the "userclose" event on the layout.
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/IBalloonLayout.xml#event-userclose
                     * @function
                     * @name onCloseClick
                     */
                    onCloseClick: function (e) {
                        e.preventDefault();

                        this.events.fire("userclose");
                    },

                    /**
                     * Used for autopositioning (balloonAutoPan).
                     * @see https://api.yandex.com/maps/doc/jsapi/2.1/ref/reference/ILayout.xml#getClientBounds
                     * @function
                     * @name getClientBounds
                     * @returns {Number[][]} The coordinates of the top left and bottom right corners of the template relative to the anchor point.
                     */
                    getShape: function () {
                        if (!this._isElement(this._$element)) {
                            return MyBalloonLayout.superclass.getShape.call(
                                this
                            );
                        }

                        var position = this._$element.position();

                        return new ymaps.shape.Rectangle(
                            new ymaps.geometry.pixel.Rectangle([
                                [position.left, position.top],
                                [
                                    position.left +
                                        this._$element[0].offsetWidth,
                                    position.top +
                                        this._$element[0].offsetHeight +
                                        this._$element.find(".arrow")[0]
                                            .offsetHeight,
                                ],
                            ])
                        );
                    },

                    /**
                     * Checking the availability of the item (in IE and Opera it might not be there yet).
                     * @function
                     * @private
                     * @name _isElement
                     * @param {jQuery} [element] Element.
                     * @returns {Boolean} Availability flag.
                     */
                    _isElement: function (element) {
                        return (
                            element && element[0] && element.find(".arrow")[0]
                        );
                    },
                }
            ),
            // Creating a nested layout for balloon content.
            MyBalloonContentLayout = ymaps.templateLayoutFactory.createClass(
                '<h3 class="popover-title">$[properties.balloonHeader]</h3>' +
                    '<div class="popover-content">$[properties.balloonContent]</div>'
            ),
            // Creating a placemark with a custom balloon layout.
            myPlacemark = (window.myPlacemark = new ymaps.Placemark(
                myMap.getCenter(),
                {
                    balloonHeader: "Balloon header",
                    balloonContent: "Balloon content",
                },
                {
                    balloonShadow: false,
                    balloonLayout: MyBalloonLayout,
                    balloonContentLayout: MyBalloonContentLayout,
                    balloonPanelMaxMapArea: 0,
                    /**
                     * Not hiding the icon when the balloon is open.
                     * hideIconOnBalloonOpen: false,
                     * And additionally moving the balloon to open over the icon.
                     * balloonOffset: [3, -40]
                     */
                }
            ));

        myMap.geoObjects.add(myPlacemark);
    });

    $(function () {
        $("#set-balloon-header").click(function () {
            window.myPlacemark.properties.set(
                "balloonHeader",
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
                    "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
            );
        });
        $("#set-balloon-content").click(function () {
            window.myPlacemark.properties.set(
                "balloonContent",
                "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
                    "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."
            );
        });
    });
    ```

{% endlist %}
