Добрый день. Хочу поделиться информацией о том как мне удалось пририсовать свою React компоненту внутрь всплывающего окна над меткой на Яндекс карте. Использовал фреймворк из этого источника.

react-yandex-maps + доки к нему

Там вполне достойная документация, но всегда чего-то не хватает, особенно когда используешь Яндекс карты в первый раз.

То, чего я добивался, показано на картинке ниже.

Как оказалось, в react-yandex-map невозможно добавить React компоненту напрямую, хотя вроде бы это вполне резонно. Есть один способ добавить уже отрисованный компонент (делается это через ReactDomServer.renderToString), но тогда это будет просто статический элемент.

Нам же нужен полноценный компонент с доступом к стейту и всему прилагающемуся функционалу React. На способ реализации данной "хотелки" меня навёл комментарий EgorKluch в issues к react-yandex-map на github, о том что он использовал ReacDom.Portal, но пример кода он так и не предоставил. Значит поверим на слово. Portal часто используют для модальных окон, внедрять которые желательно чуть ли не на тэг <body>. Это было уже что-то, туда я и стал копать.

Для начала был создан / скопирован cамодостаточный элемент Portal, который позже будет использован по назначению.

import React, {useEffect} from 'react';
import {createPortal} from 'react-dom';


export const Portal =
    ( { children, getHTMLElementId } ) => {
        // находим искомый HTML по id
        const mount = document.getElementById(elementId)
        // создаём свой div
        const el = document.createElement('div')

        useEffect(() => {
            // добавляем свой див к искомому элементу
            if (mount) mount.appendChild(el)
            return () => {
                // удаляем элемент от искомого при завершении компоненты
                if (mount) mount.removeChild(el)
            }
        }, [ el, mount ])
       
        // отменяем отрисовку при отсутствии искомого элемента
        if (!mount) return null
        // собственно, пририсовываем React-элемент в div к искомому HTML
        return createPortal(children, el)
    }

Потом, собственно, сама реализация:

import React from 'react'
import './yandex-map-restyle-ballon.scss' // стили для карты и балуна
import {Map, Placemark} from 'react-yandex-maps'


export const MapSection = () => {

  const [ activePortal, setActivePortal ] = useState(false)

  return (
    <section className={'map-section'}>
			<Map
         state={ {
         	center:{[55.773733, 37.608771]}, // координаты центра карты
         	zoom: 10, // зум он и есть зум
         	// включаем модули, отвечающие за всплывающие окна над геообъектами
         	modules={ [ 'geoObject.addon.balloon', 'geoObject.addon.hint' ] }
        >
            // Рисуем метку
            <Placemark geometry={ [55.847973, 37.692542] }
              options={
                {
                  preset: 'islands#circleIcon', // список темплейтов на сайте яндекса
                  iconColor: 'green', // цвет иконки, можно также задавать в hex
                } }
              properties={
                {
                iconContent: '2', // пару символов помещается
                hintContent: '<b> Я появляюсь при наведении на метку </b>',
                // создаём пустой элемент с заданными размерами
                balloonContent: '<div id="driver-2" class="driver-card"></div>',
              }	}
              onClick={ () => {
              // ставим в очередь промисов, чтобы сработало после отрисовки балуна
              setTimeout(() => { setActivePortal(true)}, 0)
              } } />
      </Map>
{/* здесь мы активируем портал */}
	{
    activePortal && <Portal getHTMLElementId={ 'driver-2' }>
											// Ставим свой компонент
                      <BallonComponent/>
										</Portal>
  }
  </section>
)

Далее, остаётся только стилизовать балун при отрисовке

.driver-card {
  width: 240px;
  height: 400px;
  // имейте ввиду, что больше 400px будет показано с прокруткой
}

.ymaps-2-1-79-balloon {
  box-shadow: none !important;
}

.ymaps-2-1-79-balloon__content {
  padding: 0 !important;
  background: transparent !important;
  margin: 0 !important;
}

.ymaps-2-1-79-balloon__layout {
  background: transparent !important;
  border: none !important;
}

Осталось зафиксировать версию яндекс-карт при оборачивании всего приложения

<YMaps version={ '2.1.79' }>
	<App></App>
</YMaps>

Вот, вроде бы, и всё. Если будут вопросы, задавайте, я уточню инфу в этой статье.

Комментарии (1)


  1. Dubium
    18.07.2022 11:49

    А если вдруг я буду динамически делать точки из поисковой строки, то можно ли дефолтный балун заменить?
    P.S: Подскажите как)