Клуб API Карт

Как показать балун по наведению на метку и скрыть по таймауту?

Пост в архиве.

Здравствуйте. Собственно проблема описано в сабже. Вот код:

let balloonLayout = ymaps.templateLayoutFactory.createClass(balloonHtml, {
  build: function () {
    this.constructor.superclass.build.call(this);
    this._$element = $('.lw-balloon', this.getParentElement());
    this.applyElementOffset();
    this._$element.find('.close').on('click', $.proxy(this.onCloseClick, this));
  },
  clear: function () {
    this._$element.find('.close').off('click');
    this.constructor.superclass.clear.call(this);
  },
  onSublayoutSizeChange: function () {
    balloonLayout.superclass.onSublayoutSizeChange.apply(this, arguments);
    if(!this._isElement(this._$element)) {
      return;
    }
    this.applyElementOffset();
    this.events.fire('shapechange');
  },
  applyElementOffset: function () {
    this._$element.css({
      left: -(this._$element[0].offsetWidth / 2),
      top: -(this._$element[0].offsetHeight + this._$element.find('.arrow')[0].offsetHeight)
    });
  },
  onCloseClick: function (e) {
    e.preventDefault();
    this.events.fire('userclose');
  },
  getShape: function () {
    if(!this._isElement(this._$element)) {
      return balloonLayout.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
            ]
          ]
        ));
  },
  _isElement: function (element) {
    return element && element[0] && element.find('.lw-balloon__list')[0];
  }
});

let pin = new ymaps.Placemark(context.coords, {}, {
              balloonShadow: false,
              balloonLayout: balloonLayout,
              iconLayout: pinLayout,
              balloonPanelMaxMapArea: 0,
              openBalloonOnClick: false,
              iconShape: {
                type: 'Rectangle',
                coordinates: [
                  [pinSize/-2, -pinSize], [pinSize/2, 0]
                ]
              }
            });
pin.events.add('mouseenter', (e) => {
  let position = e.get('target').geometry.getCoordinates();
  let balloon = e.get('target').balloon;
  let timeout = 1500;
  let timeoutId;
  balloon.open(position);

  let makeTimeout = () => {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(function () {
      pin.events.remove('mouseleave');
      balloon.events.remove('mouseleave');
      balloon.close();
    }, timeout);
  };

  pin.events.add('mouseleave', () => {
    makeTimeout();
  });
  balloon.events.add('mouseenter', () => {
    clearTimeout(timeoutId);
  });
  balloon.events.add('mouseleave', () => {
    makeTimeout();
  });
});

pinCollection.add(pin);

Теперь о конкретных проблемах:
1) В текущей реализации балун отображается не над меткой, а в левом верхнем углу карты. Как это исправить? (балун имеет кастомный лейаут, может ли влияеть на его поведение position:absolute?).
2) Когда срабатывает таймаут балун не закрывается, а в консоль падает ошибка

Uncaught TypeError: Cannot read property 'destroy' of null

Что я делаю не так? Спасибо.

7 комментариев
1. https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IPointGeometryAccess-docpage/#getCoordinates
Если вы меняете макет самого балуна, а не его содержимого, то да, вам надо его позиционировать.
2. Я не вижу в документации на балун событий mouseenter/mouseleave
3. В АПИ вы не можете отписаться от события, не передав обработчик, вот это всё ничего не дает:
pin.events.remove('mouseleave');
balloon.events.remove('mouseleave');

> Что я делаю не так? Спасибо.
Вы не читали документацию и не смотрели примеры
https://tech.yandex.ru/maps/jsbox/2.1/balloon_autopan
Я дополнил код.
>1. Если вы меняете макет самого балуна, а не его содержимого, то да, вам надо его позиционировать.
- Позиционирование из примера у меня было, но оно не работает.

>2. Я не вижу в документации на балун событий mouseenter/mouseleave
- А но мне нужно его скрывать, как же быть привязаться к dom?

>3. В АПИ вы не можете отписаться от события, не передав обработчик, вот это всё ничего не дает:
pin.events.remove('mouseleave');
balloon.events.remove('mouseleave');
- Подскажите пожалуйста как нужно отписываться? И почему тогда даже не работает balloon.close(); ?
lw-metrika,
1. Значит вы что-то не правильно делаете.
2. Слушать DOM нужно в макете и кидать на нем событие "userclose"
3. https://tech.yandex.ru/maps/doc/jsapi/2.1/ref/reference/IEventManager-docpage/#remove
dimik,
1. Так а что неправильно? Я дополнил код. Чего-то ещё не хватает?
2. Ок спасибо.
3. Так ведь я и делаю remove, разве не так?
lw-metrika,
1. Я не могу сказать по коду в посте, соберите минимальный пример на jsfiddle.
3. Там написано, что второй параметр (callback) – обязательный
dimik,
http://codepen.io/etomarat/pen/bpvKEZ
Собрал пример. Пока без remove даже.
lw-metrika,
http://codepen.io/anon/pen/KzoGQd?editors=0010