Hacemos un plan de terreno interactivo en 15 minutos.


En la Tostadora, a menudo preguntan cómo hacer un diagrama interactivo de una casa, un plan de su estructura interna, la capacidad de seleccionar pisos o apartamentos con información sobre ellos, mostrar información sobre los detalles de un producto en particular cuando pasa el mouse sobre ellos en una fotografía, etc. No se trata de un modelo tridimensional, sino de una imagen con la capacidad de resaltar ciertos detalles. Todas estas tareas son similares y se resuelven de manera bastante simple, pero aun así siguen apareciendo preguntas, por lo que hoy veremos cómo se hacen tales cosas usando SVG, un editor gráfico y una pizca de JavaScript.


La elección de SVG se debe al hecho de que esta es la opción más simple para el desarrollo y la depuración. Conocí a personas que aconsejaron que todo esto se hiciera en lienzo, pero allí es mucho más difícil entender lo que está sucediendo, y las coordenadas de todos los puntos en las curvas deben calcularse de antemano de alguna manera, pero aquí abrí las herramientas del desarrollador e inmediatamente vi toda la estructura, todos los objetos, con el que hay interacción, y todo lo demás se puede hacer clic con el mouse en una interfaz amigable para los humanos. El rendimiento entre el lienzo 2D habitual y SVG difícilmente diferirá. WebGL puede dar algo de bonificación a este respecto, pero el cronograma de desarrollo aumentará significativamente, sin mencionar el soporte adicional, que no siempre se ajusta al presupuesto. Incluso diría "nunca encaja".


En este tutorial, haremos algo así como un widget para un sitio ficticio para alquilar casas privadas en alguna área, pero está claro que los principios para crear esas cosas en cuestión se aplican a cualquier tema.


Empezando


Mostraré todo con Inkscape como ejemplo, pero se pueden realizar las mismas acciones en cualquier otro editor usando funciones similares.


Para trabajar, necesitamos dos cuadros de diálogo principales:


  • Editor XML (Ctrl + Shift + X o un icono con corchetes angulares): para ver la estructura del documento en forma de marcado y editar elementos individuales.
  • Relleno y trazo (Ctrl + Shift + F o el icono con un pincel en el marco), principalmente para rellenar contornos.

Inícielos inmediatamente y proceda a la creación del documento.


Si los arrastró accidentalmente a una ventana separada, puede hacer clic debajo del marco superior de esta ventana (donde no hay nada) y arrastrarlos nuevamente a la ventana principal. Esto no es del todo intuitivo, sino más bien conveniente.

Abra una foto con una vista del área. Podemos optar por insertar la imagen en sí misma como una cadena base64 o un enlace externo. Como es grande, seleccione el enlace. Luego cambiaremos el camino hacia la imagen con nuestras manos al introducir todo en las páginas del sitio. Se crea un documento SVG en el que la foto se incrustará mediante la etiqueta de imagen.


Para las imágenes ráster incrustadas en SVG, incrustadas en HTML, será posible utilizar la carga diferida, así como también las imágenes normales en las páginas. En este ejemplo, no nos detendremos en tales optimizaciones, pero no nos olvidemos de ellas en el trabajo práctico.

En la etapa actual, vemos frente a nosotros algo como esto:



Ahora cree una nueva capa (Ctrl + Shift + N o Menú> Capa> Agregar capa). En el editor XML, vemos que ha aparecido el elemento g regular. Si bien no hemos llegado lejos, podemos darle una clase, que usaremos más adelante en los scripts.


No confíes en la identificación. Cuanto más compleja es la interfaz, más fácil es hacer que se repitan y obtener errores extraños. Y en nuestra tarea, todavía no tienen ningún beneficio. Por lo tanto, las clases o los atributos de datos son nuestra elección.

Si observa detenidamente la estructura del documento en el editor XML, notará que hay muchas cosas superfluas. Cualquier editor de gráficos vectoriales más o menos complejo agregará algo propio a los documentos. Eliminar todo esto con las manos es una tarea larga e ingrata, el editor constantemente agregará algo nuevamente. Por lo tanto, la limpieza de SVG de la basura se realiza solo al final del trabajo. Y preferiblemente en una forma automatizada, ya que hay opciones preparadas, el mismo svgo, por ejemplo.


Encuentre una herramienta llamada Dibujar curvas Bezier y Líneas rectas (Shift + F6). Con ella, dibujaremos contornos cerrados alrededor de los objetos. En nuestra tarea, necesitamos delinear todos los edificios. Por ejemplo, nos limitamos a seis, pero en condiciones reales valdría la pena preasignar el tiempo para delinear con precisión todos los objetos necesarios. Aunque a menudo sucede que hay muchas entidades similares, los mismos pisos en un edificio pueden ser absolutamente idénticos. En tales casos, puede acelerar un poco y copiar y pegar las curvas.


Después de haber rodeado los edificios necesarios, volvemos al editor XML, agregamos clases o, lo más probable, será aún más conveniente, atributos de datos con índices para ellos (es posible con direcciones, pero dado que tenemos un área ficticia, solo hay índices), y mueva todo a la capa creada anteriormente para que todo esté "dispuesto en los estantes". Y la imagen, por cierto, también será útil para moverse allí, de modo que todo esté en un solo lugar, pero estos son pequeños detalles.


Ahora, habiendo elegido un camino, una curva alrededor del edificio, puede seleccionarlos todos con Ctrl + A o Menú> Editar> Seleccionar todo y editar al mismo tiempo. Debe pintarlos a todos en la ventana Relleno y trazo, y al mismo tiempo eliminar el trazo adicional allí. Bueno, o agréguelo si lo necesita por razones de diseño.



Tiene sentido pintar todos los contornos con un poco de color con un valor de opacidad mínimo para ellos, incluso si esto no es necesario en términos de diseño. El hecho es que los navegadores "inteligentes" creen que no puede hacer clic en un camino vacío, sino en uno inundado, puede hacerlo, incluso si nadie ve este relleno.

En nuestro ejemplo, dejaremos un pequeño resaltado en blanco para ver mejor con qué edificios trabajamos, guardar todo y movernos sin problemas al navegador y al editor de código más familiar.


Ejemplo básico


Creemos una página html vacía, peguemos el SVG resultante directamente y agreguemos algo de CSS para que nada salga de la pantalla. No hay nada que comentar.


.map { width: 90%; max-width: 1300px; margin: 2rem auto; border: 1rem solid #fff; border-radius: 1rem; box-shadow: 0 0 .5rem rgba(0, 0, 0, .3); } .map > svg { width: 100%; height: auto; border-radius: .5rem; } 

Recordamos que agregamos clases a los edificios y los usamos para que CSS esté más o menos estructurado.


 .building { transition: opacity .3s ease-in-out; } .building:hover { cursor: pointer; opacity: .8 !important; } .building.-available { fill: #0f0 !important; } .building.-reserved { fill: #f00 !important; } .building.-service { fill: #fff !important; } 

Como definimos estilos en línea en Inkscape, necesitamos interrumpirlos en CSS. ¿Sería más conveniente hacer todo en CSS? Si y no Depende de la situación. A veces no hay elección. Por ejemplo, si un diseñador dibujó una gran cantidad de todo colorido y lo envolvió todo en CSS e infló a la imposibilidad de que de alguna manera no se volviera il faut. En este ejemplo, uso la opción "inconveniente" para mostrar que no da mucho miedo en el contexto del problema que se está resolviendo.



Supongamos que recibimos datos nuevos sobre las casas y les agregamos diferentes clases, según su estado actual:


 const data = { id_0: { status: 'service' }, id_1: { status: 'available' }, id_2: { status: 'reserved' }, id_3: { status: 'available' }, id_4: { status: 'available' }, id_5: { status: 'reserved' }, messages: { 'available': '  ', 'reserved': '', 'service': '  1-2 ' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; building.classList.add(`-${status}`); } 

Obtenemos algo como esto:



Algo similar a lo que necesitamos ya es visible. En esta etapa, hemos resaltado objetos en el terreno que responden al desplazamiento del mouse. Y no es difícil agregarles una respuesta a un clic del mouse a través del addEventListener estándar.


Línea líder


A menudo existe la tarea de hacer líneas que conecten los objetos en el mapa y algunos elementos de la página con información adicional, así como hacer una mínima información sobre herramientas al pasar el cursor sobre estos mismos objetos. Para resolver estos problemas, la mini biblioteca de línea líder es muy adecuada, lo que crea flechas vectoriales para todos los gustos y colores.


Agreguemos precios para la información sobre herramientas a los datos y dibujemos estas líneas.


 const data = { id_0: { price: '3000', status: 'service' }, id_1: { price: '3000', status: 'available' }, id_2: { price: '2000', status: 'reserved' }, id_3: { price: '5000', status: 'available' }, id_4: { price: '2500', status: 'available' }, id_5: { price: '2500', status: 'reserved' }, messages: { 'available': '  ', 'reserved': '', 'service': '   (1-2 )' } }; const map = document.getElementById('my-map'); const buildings = map.querySelectorAll('.building'); const info = map.querySelector('.info'); const lines = []; for (building of buildings) { const id = building.getAttribute('data-building-id'); const status = data[`id_${id}`].status; const price = data[`id_${id}`].price; building.classList.add(`-${status}`); const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top' } ); lines.push(line); } 

Como puede ver, no pasa nada complicado. La línea tiene "puntos de fijación" a los elementos. Las coordenadas de estos puntos en relación con los elementos suelen ser convenientes para determinar en porcentaje. En general, hay muchas opciones diferentes, no tiene sentido enumerar y recordar, por lo que le recomiendo que solo consulte la documentación. Una de estas opciones, startLabel, será necesaria para que podamos crear una pequeña información sobre herramientas con un precio.


 const line = new LeaderLine( LeaderLine.pointAnchor(building, { x: '50%', y: '50%' }), LeaderLine.pointAnchor(info, { x: '50%', y: 0 }), { startLabel: LeaderLine.captionLabel(`${price}/`, { fontFamily: 'Rubik Mono One', fontWeight: 400, offset: [-30, -50], outlineColor: '#555' }), color: '#fff', startPlug: 'arrow1', endPlug: 'behind', endSocket: 'top', hide: true } ); 

Nadie se molesta en dibujar todos los consejos en un editor gráfico. Si se supone que tienen un contenido consistente, incluso puede ser conveniente. Especialmente si hay un deseo de pedirles diferentes posiciones para diferentes objetos.

También podemos agregar la opción de ocultar para que todas las líneas no se muestren como una escoba. Les mostraremos uno a la vez cuando pase el cursor sobre los edificios a los que corresponden:


 building.addEventListener('mouseover', () => { line.show(); }); building.addEventListener('mouseout', () => { line.hide(); }); 

Aquí puede mostrar información adicional (en nuestro caso, simplemente el estado actual del objeto) en el lugar para obtener información. Resultará casi lo que se necesita:



Esas cosas rara vez están diseñadas para dispositivos móviles, pero vale la pena recordar que a menudo se hacen a pantalla completa en el escritorio, e incluso con algunos paneles en el lateral para obtener información adicional y necesita estirar todo maravillosamente. Algo como esto, por ejemplo:


 svg { width: 100%; height: 100%; } 

En este caso, las proporciones del elemento SVG definitivamente no coincidirán con las proporciones de la imagen en el interior. Que hacer


Proporciones no coincidentes


Lo primero que viene a la mente es el ajuste de objeto: propiedad de cubierta de CSS. Pero hay un punto: no puede trabajar con SVG. E incluso si funcionó, entonces las casas a lo largo de los bordes del plan podrían salir de los bordes del esquema y volverse completamente inaccesibles. Así que aquí tienes que ir un poco más complicado.


Primer paso SVG tiene un atributo preserveAspectRatio , que es algo similar a la propiedad de ajuste de objeto (no del todo, por supuesto, pero ...). Al preserveAspectRatio="xMinYMin slice" para el elemento SVG principal de nuestro plan, obtenemos un circuito extendido sin vacíos en los bordes y sin distorsión.


Paso dos Debe arrastrar y soltar con el mouse. Técnicamente, todavía tenemos esa oportunidad. Aquí la tarea es más complicada, especialmente para principiantes. En teoría, tenemos eventos estándar para el mouse y la pantalla táctil que se pueden procesar y obtener el valor de cuánto se debe mover el mapa. Pero en la práctica, puede quedar atrapado en esto durante mucho tiempo. Hammer.js vendrá al rescate : otra pequeña biblioteca que toma toda la cocina interna sobre sí misma y proporciona una interfaz simple para trabajar con arrastrar y soltar, deslizar, etc.


Necesitamos mover la capa con los edificios y la imagen en todas las direcciones. Hazlo fácil:


 const buildingsLayer = map.querySelector('.buildings_layer'); const hammertime = new Hammer(buildingsLayer); hammertime.get('pan').set({ direction: Hammer.DIRECTION_ALL }); 

Por defecto, hammer.js también incluye el reconocimiento de svaypas, pero no los necesitamos en el mapa, así que apáguelos de inmediato para no engañarnos:


 hammertime.get('swipe').set({ enable: false }); 

Ahora debe comprender de alguna manera qué es exactamente lo que necesita publicar para mover el mapa solo a sus bordes, pero no más. Con una representación simple de dos rectángulos en nuestra cabeza, entendemos que para esto necesitamos descubrir la sangría de la capa con edificios del elemento padre (SVG en nuestro caso) desde los cuatro lados. GetBoundingClientRect viene al rescate:


 const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, }; 

¿Y por qué todavía no tenemos una forma más civilizada (y de trabajo estable) para hacer esto? Obtener getBoundingClientRect cada vez es muy malo en términos de rendimiento, pero la elección no es muy rica y es casi imposible notar la inhibición, por lo que no obtendremos optimizaciones prematuras donde todo funcione bien. De una forma u otra, esto nos permite verificar la posición de la capa con los edificios y mover todo solo si tiene sentido:


 let translateX = 0; let translateY = 0; hammertime.on('pan', (e) => { const layer = buildingsLayer.getBoundingClientRect(); const parent = svg.getBoundingClientRect(); const offsets = { top: layer.top - parent.top, bottom: layer.bottom - parent.bottom, right: layer.right - parent.right, left: layer.left - parent.left, }; const speedX = e.velocityX * 10; const speedY = e.velocityY * 10; if (speedX > 0 && offsets.left < 0) { //  } else if (speedX < 0 && offsets.right > 0) { //  } if (speedY > 0 && offsets.top < 0) { //  } else if (speedY < 0 && offsets.bottom > 0) { //   } buildingsLayer.setAttribute('transform', `translate(${translateX} ${translateY})`); }); 

En los bordes, generalmente vale la pena reducir la velocidad para que no haya paradas o sacudidas repentinas. Por lo tanto, todo va y viene en algo como esto:


 if (speedX < -offsets.left) { translateX += speedX; } else { translateX += -offsets.left * speedX / 10; } 

Hay muchas opciones para ralentizaciones. Este es el más fácil. Y sí, no es muy hermoso, pero es tonto y claro. Los coeficientes en tales ejemplos generalmente se seleccionan a simple vista, dependiendo del comportamiento deseado de la tarjeta.


Si abre un navegador y juega con el tamaño de la ventana en las herramientas del desarrollador, puede encontrar que algo salió mal ...


Fuerzas impuras


En dispositivos de escritorio, todo funciona, pero en dispositivos móviles, ocurre magia, es decir, en lugar de mover el mapa, el elemento del cuerpo se mueve. Oooooooo! Solo el elenco allí no es suficiente. Aunque está bien, esto sucede porque algo se está desbordando en algún lugar y no se ha configurado una sobrescritura: oculto. Pero en nuestro caso, puede suceder que nada se mueva en absoluto.


Un acertijo para las máquinas de escribir verdes: está el elemento g, dentro del elemento svg, dentro del elemento div, dentro del elemento del cuerpo, dentro del elemento html. Doctype naturalmente html. Si agrega transform: translate (...) para arrastrar el elemento g, entonces en la computadora portátil se moverá, como estaba previsto, pero en el teléfono ni siquiera se moverá. No hay errores en la consola. Pero definitivamente hay un error. El navegador es el último Chrome tanto allí como allí. La pregunta es por qué.

Le sugiero que piense unos 10 minutos sin Google antes de ver la respuesta.



La respuesta

Jaja Te engañe Más precisamente, no es así. Describí lo que observaríamos con las pruebas manuales. Pero en realidad, todo funciona como debería. Esto no es un error, sino una característica relacionada con la propiedad CSS de la acción táctil . En el contexto de nuestra tarea (¡de repente!) Se revela que existe y, además, tiene un cierto valor que rompe toda la lógica de interacción con el mapa. Entonces tratamos con él muy groseramente:


 svg { touch-action: none !important; } 

Pero volvamos a nuestras ovejas y veamos el resultado (es mejor, por supuesto, abrir en una pestaña separada):



Decidí no personalizar el código para ninguno de los marcos de moda, de modo que permanezca en la forma de un espacio en blanco neutral sin forma, desde el cual puede construir al crear sus componentes.


Cual es el resultado?


Después de pasar bastante tiempo, hicimos un plan en el que hay una imagen rasterizada, destacando sus diversos detalles, conectando objetos no relacionados con flechas y reacciones al mouse. Espero haber logrado transmitir la idea básica de cómo se hace todo esto en la versión de "presupuesto". Como notamos al comienzo del artículo, hay muchas aplicaciones diferentes, incluidas aquellas que no están relacionadas con algún tipo de sitios de diseño confusos (aunque este enfoque se usa con mucha frecuencia en ellos). Bueno, si está buscando algo para leer sobre cosas interactivas, pero ya tridimensionales, le dejo un enlace a un artículo sobre el tema: presentaciones tridimensionales de productos en Three.js para los más pequeños .

Source: https://habr.com/ru/post/478698/


All Articles