Estados de asiento personalizados (Renderer SDK)
Los estados de asiento personalizados te permiten marcar visualmente asientos individuales con un estado arbitrario, definido por la aplicación – por ejemplo reserved, held, vip o sold – sobre lo que el renderer ya esté dibujando. Un estado es una clave de texto: asignas esa clave a un conjunto de asientos en tiempo de ejecución con setSeatsState, y describes cómo debe verse la clave en el theme.seatStyles del renderer.
Esta guía cubre el Renderer SDK del lado del cliente (@seatmap.pro/renderer). Explica cómo aplicar y visualizar estados desde el navegador. Si buscas el ciclo de vida del asiento en el backend y los tipos de hold definidos por el cliente (el modelo de datos state / holdType en el booking service), consulta en su lugar Estados de asiento y tipos de hold – ambos son complementarios.
Visión general
La funcionalidad está construida a partir de dos capas independientes. Mantenerlas separadas en tu mente es la clave para usar la API correctamente.
| Capa | Qué controla | Cómo la manejas |
|---|---|---|
| Motor de asignación | Qué asientos están en qué estado | setSeatsState(seats, key) / clearSeatsState(seats) |
| Configuración de estilos | Cómo se ve una clave de estado | theme.seatStyles (declarativo, fijado en la construcción) |
El motor de asignación mantiene un mapeo de ID de asiento a una única clave de estado (como máximo una clave por asiento). No sabe nada sobre colores, tamaños ni el DOM. La configuración de estilos mapea cada clave de estado a una descripción de estilo y vive en los ajustes del renderer que pasas en el momento de la construcción; nunca es mutada en tiempo de ejecución por los métodos de estado.
Un asiento produce una superposición visible solo cuando ambas capas concuerdan: el motor le ha asignado una clave, y seatStyles tiene una entrada para esa clave.
La superposición es una capa DOM
Lo que en realidad aparece en pantalla es una superposición DOM: un único contenedor <div> posicionado de forma absoluta, añadido al elemento anfitrión del renderer, con un pequeño nodo <div> por cada asiento visible y asignado. No se pinta en el escenario WebGL ni Canvas2D. Como solo lee la transformación de viewport compartida (scale / translate), funciona de forma idéntica tanto bajo el renderer WebGL como bajo el respaldo Canvas2D. El contenedor usa pointer-events: none, por lo que nunca intercepta la entrada del canvas.
Inicio rápido
El flujo de extremo a extremo más pequeño: construir un renderer con un estado personalizado en theme.seatStyles, cargar un evento, luego asignar ese estado a algunos asientos después de que los asientos existan.
import { SeatmapBookingRenderer } from '@seatmap.pro/renderer';
const element = document.getElementById('seatmap') as HTMLElement;
const renderer = new SeatmapBookingRenderer(element, {
publicKey: 'your-public-key',
theme: {
seatStyles: {
mykey: {
tint: 'rgba(76,141,255,0.5)',
keepSeatName: true,
},
},
},
});
void renderer.loadEvent('event-guid').then(() => {
renderer.setSeatsState([101, 102, 103], 'mykey');
});
Notas:
publicKeyes obligatorio en tiempo de ejecución paraSeatmapBookingRenderer; el constructor lanza'Public key is undefined'si falta.setSeatsStatedebe llamarse después de que los asientos existan – es decir, después de queloadEventse resuelva (o desde dentro de un callbackonSchemaDataLoaded). Los estados se aplican a asientos que ya se han cargado.- Los nodos de superposición son diminutos con el zoom de ajuste al recinto. Para hacerlos claramente visibles, acerca el zoom a una sección con
zoomToSection(consulta los ejemplos trabajados).
Conceptos
Semántica del motor de asignación
- Un estado por asiento. Cada asiento tiene exactamente una clave asignada, o ninguna.
- Sobrescritura, no fusión. Asignar un nuevo estado a un asiento descarta cualquier asignación previa. Asignar
reservedy luegosoldal mismo asiento lo deja comosold. - La limpieza es por asiento y silenciosa ante fallos. Limpiar el estado en IDs que no estaban asignados simplemente los omite; una limpieza parcial deja los demás asientos intactos.
- El bloqueo es una compuerta aparte. Un asiento se trata como bloqueado solo cuando tiene una asignación y el estilo de esa clave establece
blockInteractioncomo verdadero. El estado por sí solo nunca implica bloqueo.
Claves integradas frente a claves personalizadas
Las claves de estado se dividen en dos grupos.
Las claves integradas son los propios estados de asiento del renderer, enumerados por BuiltinSeatStateKey:
type BuiltinSeatStateKey =
| 'default'
| 'unavailable'
| 'filtered'
| 'hovered'
| 'selected'
| 'loading'
| 'error';
Estas se configuran con el tipo más rico ISeatStyle (tamaño, color, trazo, sombra, sobrescritura accesible). Los temas de booking y admin que vienen incluidos ya las pueblan. En particular, el size de la entrada default es la huella que usa cada nodo de superposición (consulta Notas de rendimiento y renderizado).
Las claves personalizadas son cualquier otra cadena: 'reserved', 'held', 'vip', 'sold', 'restricted', etcétera. Las asignas mediante setSeatsState y las estilizas con el más ligero ICustomSeatStyle (o un ISeatStyle completo si lo prefieres).
La configuración de estilos
Toda la estilización de asientos vive en theme.seatStyles, tipada como SeatStylesMap.
Forma de SeatStylesMap
type SeatStylesMap = Partial<Record<BuiltinSeatStateKey, ISeatStyle>> & {
[customKey: string]: ICustomSeatStyle | ISeatStyle | undefined;
};
- La mitad
Partial<Record<BuiltinSeatStateKey, ISeatStyle>>restringe las siete claves integradas aISeatStyle(el tipo más rico, que añade la sobrescrituraaccessible). Las siete son opcionales. - La mitad de la firma de índice abre el mapa a cualquier clave de cadena arbitraria, cuyo valor puede ser un
ICustomSeatStyle(ligero: tint, svg, callback de render, etc.) o unISeatStylecompleto. Elundefineden la unión es requerido por TypeScript al mezclar unPartial<Record<...>>con una firma de índice general.
En la práctica: las claves integradas obtienen ISeatStyle; las claves personalizadas suelen usar el más ligero ICustomSeatStyle.
IBasicSeatStyle
interface IBasicSeatStyle {
size: number;
color: string;
seatName?: {
font: string;
color: string;
};
stroke?: {
width: number;
color: string;
align: 'center' | 'inside' | 'outside';
};
imageId?: string;
shadow?: {
blur: number;
color: string;
x?: number;
y?: number;
};
}
| Campo | Tipo | Opcional | Significado |
|---|---|---|---|
size |
number |
No | Tamaño de renderizado del asiento (diámetro/radio en la unidad del renderer). |
color |
string |
No | Color de relleno del asiento. |
seatName |
{ font: string; color: string } |
Sí | Tipografía de la etiqueta del asiento. font es una cadena de fuente CSS (por ejemplo '18px Arial'); color es el color del texto. |
stroke |
{ width: number; color: string; align: 'center' | 'inside' | 'outside' } |
Sí | Borde del asiento: grosor, color y alineación respecto al borde del asiento. |
imageId |
string |
Sí | ID de un recurso de imagen preregistrado dibujado sobre el asiento. |
shadow |
{ blur: number; color: string; x?: number; y?: number } |
Sí | Sombra paralela: radio de desenfoque, color y desplazamiento X/Y opcional. |
ISeatStyle
interface ISeatStyle extends IBasicSeatStyle {
accessible?: IBasicSeatStyle;
}
| Campo | Tipo | Opcional | Significado |
|---|---|---|---|
(hereda todos los campos de IBasicSeatStyle) |
Consulta la tabla anterior. | ||
accessible |
IBasicSeatStyle |
Sí | Estilo de sobrescritura aplicado cuando el asiento está marcado como accesible/para silla de ruedas. Lleva los mismos seis campos base, menos el propio accessible. |
ICustomSeatStyle
interface ICustomSeatStyle {
tint?: string;
svg?: string;
imageId?: string;
className?: string;
priority?: number;
blockInteraction?: boolean;
keepSeatName?: boolean;
render?: (args: ISeatStateRenderArgs) => HTMLElement | string;
}
| Campo | Tipo | Opcional | Significado |
|---|---|---|---|
tint |
string |
Sí | Tinte de color superpuesto sobre la huella del asiento (renderizado como un círculo relleno). |
svg |
string |
Sí | SVG a dibujar sobre el asiento. Si empieza con <, se trata como marcado SVG en línea; de lo contrario se usa como URL. |
imageId |
string |
Sí | ID de un recurso de imagen preregistrado (buscado en theme.images[imageId]). |
className |
string |
Sí | Nombre de clase CSS añadido al <div> de superposición del asiento (para estilización / animación personalizada). |
priority |
number |
Sí | Aceptado por el tipo pero actualmente no consumido por la superposición. No hay orden en z, y establecerlo no tiene efecto hoy en día. |
blockInteraction |
boolean |
Sí | Cuando es true, el asiento se reporta como bloqueado (consulta Bloqueo de interacción). |
keepSeatName |
boolean |
Sí | Controla la etiqueta del asiento en el nodo de superposición. La etiqueta se muestra por defecto; solo keepSeatName: false la suprime. |
render |
(args: ISeatStateRenderArgs) => HTMLElement | string |
Sí | Callback DOM personalizado que suministra por completo el contenido de la superposición del asiento. |
Nota: ICustomSeatStyle no tiene un size propio. Los nodos de superposición comparten la huella de asiento por defecto – el tamaño proviene de seatStyles['default']?.size (con respaldo a 10 si no hay entrada default).
ISeatStateRenderArgs
El único argumento pasado a un callback render personalizado:
interface ISeatStateRenderArgs {
seat: ISeat;
stateKey: string;
style: ICustomSeatStyle;
sizePx: number;
}
| Campo | Tipo | Significado |
|---|---|---|
seat |
ISeat |
El objeto de datos completo del asiento que se está renderizando. |
stateKey |
string |
La clave de estado activa (integrada o personalizada) que disparó este renderizado. |
style |
ICustomSeatStyle |
El ICustomSeatStyle resuelto para este estado, para que el callback pueda inspeccionar su propia configuración. |
sizePx |
number |
El tamaño renderizado actual del asiento en píxeles, para que el callback pueda escalar su salida. |
La API pública
Ambos métodos son públicos en el renderer base, por lo que SeatmapBookingRenderer y SeatmapAdminRenderer los exponen de forma idéntica. Aplica estados solo después de que el evento y sus asientos se hayan cargado.
setSeatsState
setSeatsState(seats: ISeat[] | number[] | string[], stateKey: string): void
Asigna stateKey a los asientos dados: resuelve los IDs de asiento, registra la asignación en el motor (sobrescribiendo cualquier estado previo), escribe customState en cada objeto de asiento coincidente, luego redibuja para que la superposición se resincronice. Si seats está vacío, la llamada no hace nada.
clearSeatsState
clearSeatsState(seats: ISeat[] | number[] | string[]): void
Limpia el estado de los asientos dados: resuelve los IDs, borra la asignación del motor (los fallos se omiten silenciosamente), elimina customState de cada objeto de asiento coincidente, luego redibuja. Si seats está vacío, la llamada no hace nada.
Formas de argumento aceptadas
Ambos métodos aceptan la misma unión para seats:
ISeat[]– objetos de asiento (resueltos por su.id).number[]– IDs numéricos de asiento en bruto.string[]– claves compuestas de asiento (resueltas a IDs internamente).
El despachador husmea el tipo del primer elemento para decidir cómo resolver los IDs.
Limpiar todos los asientos
No hay una sobrecarga “limpiar todo” sin argumentos en el renderer. Para limpiar todos los asientos, pasa todos los IDs de asiento explícitamente mediante getSeats:
renderer.clearSeatsState(renderer.getSeats().map((s) => s.id));
Definir estados personalizados
Un estado personalizado es una entrada de seatStyles bajo una clave arbitraria. Combina los campos de ICustomSeatStyle:
- Tint / color.
tintsuperpone un círculo relleno de un color CSS sobre la huella del asiento. (Para claves integradas estilizadas conISeatStyle,colores el relleno del asiento.) - Size. Un estado personalizado no tiene tamaño independiente; comparte la huella de asiento
default(seatStyles['default']?.size, respaldo10).ISeatStateRenderArgs.sizePxreporta el valor resuelto. - Stroke. Disponible en
IBasicSeatStyle/ISeatStyle(width,color,align). Usa una entradaISeatStylecompleta si la necesitas. - Shadow. Disponible en
IBasicSeatStyle/ISeatStyle(blur,color,x,y). - Nombre de asiento /
keepSeatName. El nodo de superposición muestra la etiqueta del asiento por defecto; establecekeepSeatName: falsepara suprimirla. La tipografía deseatName(font,color) forma parte delISeatStyleintegrado. - className. Añade una clase CSS al
<div>de superposición para que puedas estilizarlo o animarlo desde tu hoja de estilos. - imageId. Dibuja un recurso de imagen preregistrado, resuelto desde
theme.images[imageId]. - priority. Declarado en
ICustomSeatStylepero actualmente no leído por la superposición. No se implementa ningún orden en z, y como cada asiento mantiene como máximo un estado, nunca hay más de un estado personalizado en un asiento que ordenar. Establecerlo no tiene efecto hoy en día. - blockInteraction. Reportado a través de la compuerta de bloqueo (consulta Bloqueo de interacción).
Ejemplo de estados personalizados:
const seatStyles: SeatStylesMap = {
restricted: {
tint: 'rgba(214,88,78,0.6)',
blockInteraction: true,
keepSeatName: false,
},
promo: {
tint: 'rgba(76,141,255,0.5)',
className: 'promo-seat',
},
};
Sobrescribir estados de asiento integrados
El mismo mapa theme.seatStyles que declara claves personalizadas también sobrescribe los propios estados integrados del renderer. La diferencia respecto a las claves personalizadas es dónde se dibujan y qué los impulsa: las claves integradas reestilizan el glifo de asiento pintado en el escenario del canvas, y el renderer las aplica automáticamente a partir de la propia condición de cada asiento (locked, filtered, hovered, selected, etcétera). No llamas a setSeatsState para ellas.
| Clave integrada | Se aplica cuando el asiento está… |
|---|---|
default |
disponible, sin hover ni seleccionado |
unavailable |
bloqueado / no en venta |
filtered |
filtrado fuera de la vista actual |
hovered |
bajo el puntero (e interactivo) |
selected |
seleccionado / en el carrito |
loading |
en estado de carga |
error |
en estado de error |
Tu seatStyles se fusiona en profundidad sobre el tema por defecto incluido en la construcción. Por lo tanto puedes sobrescribir un único campo de un estado integrado y heredar el resto – no hay necesidad de reformular todo el estilo. Por ejemplo, para hacer los asientos seleccionados más grandes y verdes con un borde más grueso, y recolorear los asientos no disponibles:
const renderer = new SeatmapBookingRenderer(element, {
publicKey: 'your-public-key',
theme: {
seatStyles: {
selected: {
size: 46,
color: '#1f9d55',
stroke: { width: 3, color: '#0b5d2e', align: 'outside' },
},
unavailable: { size: 8, color: '#d0d0d0' },
},
},
});
Notas:
- Las claves integradas toman el más rico
ISeatStyle(size,color,seatName,stroke,imageId,shadow,accessible). Suministra solo los campos que quieras cambiar; el resto proviene del tema por defecto mediante la fusión en profundidad. - Esto estiliza el glifo de asiento del canvas, independiente de la superposición DOM de estado personalizado. Ambos se componen: un asiento puede dibujarse como
selecteden el canvas y aun así llevar una superposición de estado personalizado encima. default.sizetambién define la huella de cada nodo de superposición de estado personalizado (consulta Notas de rendimiento y renderizado), por lo que cambiarlo redimensiona tanto los asientos base como las superposiciones.
Receta: colores por precio, imágenes de estado y números de asiento juntos
Una necesidad común es colorear los asientos por nivel de precio (para que los compradores vean el precio) y superponer un estado transaccional – reserved, held, sold – como un ícono o máscara SVG encima, manteniendo el número de asiento legible. Dibujado como un único glifo de canvas, eso se pelea consigo mismo:
- Una imagen dibujada directamente sobre el asiento (mediante
imageIden el estilo del asiento, o una sobrescrituraonBeforeSeatDraw) reemplaza el glifo completo, por lo que el número de asiento desaparece – la ruta de dibujo del asiento retorna en cuanto se dibuja la imagen y nunca pinta la etiqueta. - Como esa imagen es el glifo del asiento, se dibuja al tamaño en píxeles propio de la imagen; dimensionarla para llenar el círculo del asiento es incómodo.
- Establecer la imagen por asiento a través de
onBeforeSeatDrawretorna unISeatStylenuevo, que reemplaza todo el estilo y descarta el color de precio/categoría que estableciste consetSeatsCategory.
La superposición de estado personalizado evita los tres problemas, porque es una capa DOM aparte dibujada encima del canvas, no un reemplazo del glifo del asiento:
- El color de precio permanece.
setSeatsStatenunca tocaseat.special.categoryni el mapa de colores de categoría, por lo que un asiento conserva el color que le asignaste consetSeatsCategory. - La imagen llena el asiento. La superposición dibuja el
svg/imageIddel estado en la huella completa del asiento (inset: 0, ancho y alto iguales al tamaño de asiento por defecto). - El número queda encima. El número de asiento se redibuja centrado sobre la imagen por defecto (
keepSeatNamepor defecto estrue); establecekeepSeatName: falsesolo cuando quieras que el ícono reemplace el número (el candado ensold, por ejemplo).
Así que reemplaza el enfoque por asiento onBeforeSeatDraw con dos llamadas independientes y componibles:
// 1) Price-tier color, painted on the canvas seat -- persists.
renderer.setSeatsCategory(seatIds, priceCategoryId, '#1f9d55');
// 2) Transactional state overlay on top -- seat number preserved.
renderer.setSeatsState(seatIds, 'reserved');
con el estado declarado una vez en la construcción. Omite tint para que el color de precio se vea a través de las partes transparentes de la máscara:
const seatStyles: SeatStylesMap = {
reserved: {
keepSeatName: true, // default -- seat number stays visible on top
svg:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">' +
'<circle cx="50" cy="50" r="42" fill="none" stroke="#fff" stroke-width="6"/></svg>',
},
};
El asiento conserva su color de precio, el SVG llena el asiento como una máscara de superposición, y el número se renderiza encima – sin onBeforeSeatDraw. Para recolorear el asiento según el estado en lugar de dejar que el color de precio se transparente, añade un tint semitransparente. Para referenciar un recurso con nombre en lugar de SVG en línea, regístralo bajo theme.images y apunta imageId a él:
const renderer = new SeatmapBookingRenderer(element, {
publicKey: 'your-public-key',
theme: {
images: { holdIcon: 'data:image/svg+xml;utf8,...' }, // or a URL
seatStyles: {
held: { imageId: 'holdIcon', keepSeatName: true },
},
},
});
La superposición es diminuta con el zoom de ajuste al recinto y está oculta en la vista detallada de una sola sección; consulta Notas de rendimiento y renderizado si un estado no aparece. Como cada asiento con estado es un nodo DOM aparte, mantén acotado el número de asientos con estado visibles a la vez – la superposición no se agrupa en lotes como los asientos del canvas, por lo que grandes cantidades degradan el rendimiento.
DOM personalizado mediante render()
Cuando los campos declarativos no bastan, suministra un callback render. Provee por completo el contenido del nodo de superposición para ese estado.
render?: (args: ISeatStateRenderArgs) => HTMLElement | string;
El callback recibe un ISeatStateRenderArgs (seat, stateKey, style, sizePx) y devuelve uno de los siguientes:
- un
HTMLElement– añadido directamente al nodo de superposición; usa esto para contenido complejo o interactivo, o - una
string– tratada como marcado SVG en bruto. El renderer la envuelve en un<div>posicionado de forma absoluta (inset: 0) y la renderiza mediante un<img src="data:image/svg+xml;utf8,...">dimensionado para llenar el nodo.
Cuando render está presente, asume la generación de contenido del nodo; la ruta declarativa tint / svg / imageId no se usa para ese estado. Usa render para visuales dinámicos, dirigidos por datos (por ejemplo, dibujar algo basado en seat o sizePx). Prefiere los campos declarativos para visuales estáticos – son más simples y no necesitan callback.
const seatStyles: SeatStylesMap = {
badge: {
render: ({ sizePx }: ISeatStateRenderArgs): string =>
`<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${sizePx} ${sizePx}">` +
`<circle cx="${sizePx / 2}" cy="${sizePx / 2}" r="${sizePx / 3}" fill="#fff"/></svg>`,
},
};
Animaciones
Hay dos rutas de animación.
Animaciones SVG autónomas (SMIL)
Los estados personalizados canónicos se animan por completo dentro de la propia cadena SVG, usando elementos SMIL (<animate>, <animateTransform>). Esto no necesita CSS externo, ni @keyframes, ni className – por ejemplo, un <animate> sobre r y stroke-opacity de un círculo produce un anillo pulsante. Este es el enfoque usado por los ejemplos trabajados a continuación.
Animaciones CSS mediante className
Si prefieres animación dirigida por CSS, establece className en el estado personalizado. El nodo de superposición recibe esa clase en su <div>, y lo animas desde tu propia hoja de estilos:
.promo-seat {
animation: promo-pulse 1.4s ease-in-out infinite;
}
@keyframes promo-pulse {
0%,
100% {
opacity: 0.4;
}
50% {
opacity: 1;
}
}
const seatStyles: SeatStylesMap = {
promo: { tint: 'rgba(76,141,255,0.5)', className: 'promo-seat' },
};
Bloqueo de interacción
Un asiento se reporta como bloqueado solo cuando se cumplen ambas condiciones:
- El asiento tiene una clave de estado asignada.
seatStyles[stateKey]existe y tieneblockInteractioncomo verdadero.
Por lo tanto:
- Un asiento sin asignación nunca está bloqueado.
- Un asiento cuyo estilo carece de
blockInteraction(o lo tiene como falso) no está bloqueado. - Si el mapa
seatStylesestá ausente, nada está bloqueado.
Concretamente, con { sold: { blockInteraction: true }, held: { tint: '#4c8dff' } }: un asiento en sold está bloqueado, un asiento en held no lo está.
keepSeatName es independiente del bloqueo. La etiqueta se muestra por defecto; solo keepSeatName: false la suprime. En los ejemplos trabajados a continuación, sold establece keepSeatName: false (el ícono de candado reemplaza el número), mientras que reserved, held y vip establecen keepSeatName: true.
Booking vs Admin Renderer
setSeatsState y clearSeatsState están definidos en el renderer base y son heredados sin cambios tanto por SeatmapBookingRenderer como por SeatmapAdminRenderer. La API, las formas de argumento y el comportamiento son idénticos en ambos.
Los constructores difieren solo en sus tipos de ajustes.
SeatmapBookingRenderer:
constructor(
element: HTMLElement,
settings?: IBookingRendererSettings,
tags?: Record<string, string | number | boolean>,
)
SeatmapAdminRenderer:
constructor(element: HTMLElement, settings?: IAdminRendererSettings)
Ambos tipos de ajustes aceptan settings.theme.seatStyles.
Por qué una demo corre sobre el admin renderer
Cuando quieres demostrar y verificar visualmente estados personalizados, el admin renderer es la superficie más fácil – por visibilidad de renderizado, no por capacidad de la API. En el booking renderer, la disponibilidad de asientos para un evento real determina la apariencia: los asientos de demo que en realidad no están disponibles se leen como no disponibles, y la estilización de no disponible puede ocultar la superposición de estado personalizado. El admin renderer no aplica esa estilización de disponibilidad de booking, por lo que los estados se muestran limpiamente. En producción, el booking renderer expone exactamente los mismos métodos para asientos genuinamente disponibles.
Ejemplos trabajados
Lo siguiente reproduce cuatro estados canónicos y el flujo completo. Es copiable y pegable.
Los cuatro estados en theme.seatStyles
const seatStyles: SeatStylesMap = {
reserved: {
tint: 'rgba(224,161,60,0.55)',
keepSeatName: true,
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#fff" stroke-width="6"><animate attributeName="r" values="34;46;34" dur="1.4s" repeatCount="indefinite"/><animate attributeName="stroke-opacity" values="0.9;0.3;0.9" dur="1.4s" repeatCount="indefinite"/></circle></svg>',
},
held: {
tint: 'rgba(76,141,255,0.5)',
keepSeatName: true,
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="42" fill="none" stroke="#fff" stroke-width="5"><animate attributeName="stroke-opacity" values="0.15;0.85;0.15" dur="2.2s" repeatCount="indefinite"/></circle></svg>',
},
vip: {
tint: 'rgba(155,108,255,0.5)',
keepSeatName: true,
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="none" stroke="#fff" stroke-width="5" stroke-dasharray="30 200" stroke-linecap="round"><animateTransform attributeName="transform" type="rotate" from="0 50 50" to="360 50 50" dur="2.4s" repeatCount="indefinite"/></circle></svg>',
},
sold: {
tint: 'rgba(214,88,78,0.6)',
blockInteraction: true,
keepSeatName: false,
svg: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="#fff" d="M12 1a5 5 0 0 0-5 5v3H6a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-9a2 2 0 0 0-2-2h-1V6a5 5 0 0 0-5-5m0 2a3 3 0 0 1 3 3v3H9V6a3 3 0 0 1 3-3"/></svg>',
},
};
Resumen por estado:
reserved–tint,keepSeatName: true, anillo pulsante animado (SMIL<animate>sobrerystroke-opacity, 1.4 s).held–tint,keepSeatName: true, anillo que se desvanece (SMIL<animate>sobrestroke-opacity, 2.2 s).vip–tint,keepSeatName: true, anillo punteado giratorio (SMIL<animateTransform>rotate, 2.4 s).sold–tint,blockInteraction: true,keepSeatName: false, ícono de candado estático (sin animación).
Ninguno de los cuatro estados usa color, size, stroke, render ni className.
Elegir una sección de demo
Agrupa los asientos por su sección y devuelve la primera sección con al menos 44 asientos, de lo contrario la más grande:
const pickCustomStateDemoSection = (
renderer: SeatmapAdminRenderer,
): { sectionId: number; ids: number[] } => {
const bySection = new Map<number, number[]>();
for (const seat of renderer.getSeats()) {
const list = bySection.get(seat.sectorId) ?? [];
list.push(seat.id);
bySection.set(seat.sectorId, list);
}
let best: { sectionId: number; ids: number[] } | null = null;
for (const [sectionId, ids] of bySection) {
if (ids.length >= 44) return { sectionId, ids };
if (!best || ids.length > best.ids.length) best = { sectionId, ids };
}
return best ?? { sectionId: 0, ids: [] };
};
El flujo de demo mixto
Limpia todos los asientos, aplica los cuatro estados en porciones disjuntas de la sección de demo, luego acerca el zoom:
const demoMix = (renderer: SeatmapAdminRenderer): void => {
const { sectionId, ids } = pickCustomStateDemoSection(renderer);
const pick = (from: number, to: number): number[] => ids.slice(from, to);
renderer.clearSeatsState(renderer.getSeats().map((s) => s.id));
renderer.setSeatsState(pick(0, 12), 'reserved');
renderer.setSeatsState(pick(12, 24), 'held');
renderer.setSeatsState(pick(24, 32), 'vip');
renderer.setSeatsState(pick(32, 44), 'sold');
renderer.zoomToSection(sectionId, { focus: true });
};
La llamada zoomToSection(sectionId, { focus: true }) es lo que hace visibles los pequeños nodos de superposición.
Acciones de un solo estado
Cada acción de un solo estado aplica un estado a su porción (sin limpiar primero), luego acerca el zoom:
const applyReserved = (renderer: SeatmapAdminRenderer): void => {
const { sectionId, ids } = pickCustomStateDemoSection(renderer);
renderer.setSeatsState(ids.slice(0, 12), 'reserved');
renderer.zoomToSection(sectionId, { focus: true });
};
Las acciones held, vip y sold siguen la misma forma, cada una sobre su propia porción de ids: held sobre ids.slice(12, 24), vip sobre ids.slice(24, 32), y sold sobre ids.slice(32, 44).
Limpiar todos los estados
const clearStates = (renderer: SeatmapAdminRenderer): void => {
const ids = renderer.getSeats().map((s) => s.id);
renderer.clearSeatsState(ids);
};
Notas de rendimiento y renderizado
La superposición mantiene el coste del DOM proporcional a lo que de verdad está en pantalla – pero es DOM real, así que el número de asientos con estado visibles al mismo tiempo gobierna el rendimiento.
- El coste escala con el número de asientos con estado visibles – mantenlo acotado. Cada asiento asignado visible monta su propio nodo
<div>, más un<img>interno para unsvg/imageId, una capatintopcional, y el nodo del número de asiento. A diferencia de la capa de asientos WebGL, estos son elementos DOM individuales, no agrupados en lotes – y los SVG animados (SMIL) ejecutan cada uno su propia animación. Un puñado o unas pocas docenas de asientos con estado en pantalla está bien; aplicar estados a cientos o miles de asientos visibles a la vez – toda una sección grande, o cualquier conjunto de estados con un zoom donde gran parte del recinto esté a la vista – crea esa cantidad de nodos y degrada significativamente la tasa de fotogramas, tanto más con animación. Para estilización que deba escalar a todo el recinto, prefiere las rutas agrupadas en el canvas: colores de precio/categoría mediantesetSeatsCategory, y sobrescrituras de estados integrados enseatStyles. Reserva la superposición de estado personalizado para un conjunto acotado de asientos que el usuario esté de verdad mirando, y prefiere visuales estáticos (no animados) cuando muchos asientos lleven un estado a la vez. - Virtualización. Un nodo se monta solo para asientos en la intersección del conjunto visible y el conjunto asignado. Los asientos fuera del viewport nunca se montan; a los asientos que salen de la vista por desplazamiento se les eliminan los nodos.
- Contenedor perezoso. No se crea ningún
<div>contenedor hasta que se asigna al menos un estado. Cuando no quedan estados, la superposición se desmonta por completo – nodos eliminados, contenedor retirado del DOM. Limpiar todos los estados elimina la superposición por completo. - Supresión en vista de sección. Cuando el renderer entra en la vista detallada de una sola sección, la superposición se suprime (
display: none) y se omite la reconciliación. Reaparece cuando se levanta la supresión. Esto está vinculado a la vista de detalle de sección, no al modo 3D. - Diminuta con el ajuste al recinto. El tamaño del nodo proviene de
seatStyles['default']?.size(respaldo10) y los nodos se posicionan en coordenadas del mundo, escaladas por la transformación de viewport compartida. Con el zoom de ajuste al recinto los nodos son muy pequeños. Por eso los ejemplos llaman azoomToSection(sectionId, { focus: true }). - Independencia del escenario. La superposición lee solo estado agnóstico al escenario – la transformación de viewport, el elemento anfitrión,
devicePixelRatioytheme.imagespara resolverimageId. Se comporta de forma idéntica bajo WebGL y el respaldo Canvas2D.
Resolución de problemas
La superposición no es visible.
- Nivel de zoom. Con el ajuste al recinto, los nodos son diminutos. Llama a
zoomToSection(sectionId, { focus: true })(o acerca el zoom a la sección de otra manera) para hacerlos visibles. - Supresión en vista de sección. En la vista detallada de una sola sección el contenedor de superposición está oculto. Regresa cuando se levanta la supresión. La rotación 3D no suprime la superposición.
- Asientos aún no cargados. El estado se aplica solo a asientos cargados. Llama a
setSeatsStatedespués de queloadEventse resuelva (o desdeonSchemaDataLoaded). - Clave incorrecta / no en
seatStyles. Un asiento solo renderiza una superposición cuando su clave asignada tiene una entradaseatStylescoincidente. Verifica que la cadena de clave pasada asetSeatsStatecoincida exactamente con una clave entheme.seatStyles.
Los estados no se limpian.
clearSeatsStatelimpia solo los IDs que pasas; los fallos se omiten silenciosamente. Para limpiar todo, pasa todos los IDs:renderer.clearSeatsState(renderer.getSeats().map((s) => s.id)). Cuando se limpia el último estado, todo el contenedor de superposición se retira del DOM.
Los asientos de demo del booking renderer aparecen como no disponibles.
- En el booking renderer, la disponibilidad real del evento estiliza los asientos no disponibles, lo que puede ocultar la superposición de estado personalizado. Demuestra los estados personalizados en
SeatmapAdminRenderer, que no aplica la estilización de disponibilidad de booking. En producción, en el booking renderer, aplica estados personalizados a asientos genuinamente disponibles.
Referencia rápida de la API
| Símbolo | Firma / forma | Dónde |
|---|---|---|
setSeatsState |
(seats: ISeat[] | number[] | string[], stateKey: string): void |
Renderer (booking + admin) |
clearSeatsState |
(seats: ISeat[] | number[] | string[]): void |
Renderer (booking + admin) |
setSeatsCategory |
(seats: ISeat[] | number[] | string[], category: number, color?: string): void |
Renderer (booking + admin) |
getSeats |
() => ISeatDTO[] (se usa para enumerar asientos al limpiar todo) |
Renderer |
zoomToSection |
(sectionId, { focus: true }) |
Renderer |
loadEvent |
booking: (eventId: string, sectorId?: number) => Promise<void>; admin: (eventId: string) => Promise<void> |
renderers |
BuiltinSeatStateKey |
'default' | 'unavailable' | 'filtered' | 'hovered' | 'selected' | 'loading' | 'error' |
type |
SeatStylesMap |
Partial<Record<BuiltinSeatStateKey, ISeatStyle>> & { [customKey: string]: ICustomSeatStyle | ISeatStyle | undefined } |
type |
IBasicSeatStyle |
{ size; color; seatName?; stroke?; imageId?; shadow? } |
interface |
ISeatStyle |
IBasicSeatStyle & { accessible?: IBasicSeatStyle } |
interface |
ICustomSeatStyle |
{ tint?; svg?; imageId?; className?; priority?; blockInteraction?; keepSeatName?; render? } |
interface |
ISeatStateRenderArgs |
{ seat: ISeat; stateKey: string; style: ICustomSeatStyle; sizePx: number } |
interface |
IRendererTheme.seatStyles |
seatStyles?: SeatStylesMap |
theme field |
IRendererTheme.images |
images?: { [id: string]: string } (recursos SVG/imagen con nombre, resueltos por imageId) |
theme field |
Relacionado
- Estados de asiento y tipos de hold – el ciclo de vida del tipo de hold en el backend y el modelo de datos (
state/holdTypeen el booking service). Ese artículo cubre cómo los asientos avanzan por su ciclo de vida del lado del servidor; este cubre cómo visualizar y aplicar estados del lado del cliente mediante el Renderer SDK. - Estados y estilización de secciones – el concepto equivalente aplicado a los contornos de sección (highlighted, selected, unavailable, filtered).
- Inicializar el SDK – instala
@seatmap.pro/renderer, construye un renderer y carga un evento antes de aplicar cualquier estado personalizado.