Эта проблема несколько раз уже поднималась в клубе API Яндекс Карт.
Оригинал статьи - тут с подсветкой кода
При открытии баллуна неправильно работает смещение карты и часть баллуна зарезается
Одним из решений было "прибивать" баллун к низу карты. Этот метод я реализовал в проекте http://pomnivoinu.ru/map (желкните на объект, затем - "Подробнее"). В этом проекте - баллун довольно большой и содержит много контента с вкладками, и юзер (по замыслу) должен задержаться на этом баллуне. Поэтому "прибивать" его к низу карты было оправдано.
Баллун прибит к низу карты
В другом проекте я также использую кастомный баллун и проблема с его показом была поднята заказчиком. Пришлось самому писать функцию расчета размера баллуна в координатах карты и размер смещения для плавного движения.
balloonLayout = ymaps.templateLayoutFactory.createClass(
// шаблон баллуна у нас лежит в тэге <script type="text/html">
$('#balloonContentTemplate').html(), {
build: function() {
// исполняем конструктор суперкласса
balloonLayout.superclass.build.call(this);
// получает геообъект
var geoObject = this.getData().geoObject,
// карту
map = geoObject.getMap(),
// координаты геообъекта
coords = geoObject.geometry.getCoordinates(),
// контейнер баллуна
container = $(this.getParentElement());
// смещаем контейнер баллуна относительно его привязки, чтобы получилось, что баллун находится по центру над геообъектом
container.find('.partner-balloon').each( function() {
$(this).css( {
left: -Math.round($(this).outerWidth() / 2),
top: -$(this).outerHeight()
});
var zoom = map.getZoom(),
width = $(this).outerWidth(),
height = $(this).outerHeight(),
projection = map.options.get('projection'),
// переводим геокоординаты геообъекта в пиксельные
global = projection.toGlobalPixels(coords, zoom),
// получаем пиксельные координаты центра карты
center = map.getGlobalPixelCenter(),
// получаем прямоугольник баллуна в пиксельных координатах
// прямоугольник смещаем над точкой посередине
balloonGlobalBounds = [ [ global[0] - Math.round(width / 2), global[1] + 0],
[ global[0] + Math.round(width / 2), global[1] - height - 17]],
bounds = map.getBounds(),
// получаем вьюпорт карты в пиксельных координатах
globalBounds = [ projection.toGlobalPixels(bounds[0], zoom),
projection.toGlobalPixels(bounds[1], zoom)],
// инициализируем смещение
pan = [ 0, 0];
// проверяем, находится ли прямоугольник баллуна внутри прямоугольника вьюпорта
// если нет, то смещаем его и прибавляем 20 пикселей для красоты
if ( balloonGlobalBounds[0][0] < globalBounds[0][0] ) {
pan[0] = balloonGlobalBounds[0][0] - globalBounds[0][0] - 20
} else if ( balloonGlobalBounds[1][0] > globalBounds[1][0]) {
pan[0] = balloonGlobalBounds[1][0] - globalBounds[1][0] + 20
}
if ( balloonGlobalBounds[0][1] > globalBounds[0][1] ) {
pan[1] = balloonGlobalBounds[0][1] - globalBounds[0][1] + 20
} else if ( balloonGlobalBounds[1][1] < globalBounds[1][1]) {
pan[1] = balloonGlobalBounds[1][1] - globalBounds[1][1] - 20
}
if (pan[0] || pan[1]) {
center[0] += pan[0];
center[1] += pan[1];
// вызываем panTo
map.panTo(projection.fromGlobalPixels(center, zoom), { delay: 0, duration: 500});
}
}).
on('click', '.ymaps-b-balloon__close', function() {
map.balloon.close();
});
},
clear: function() {
$('.partner-balloon').off();
balloonLayout.superclass.clear.call(this);
}
})
в результате получаем правльное расположение баллуна