Realizzare animazioni con Blazor Server

di Moreno Gentili, in ASP.NET Core,

In un precedente script abbiamo visto come usare Blazor Server per inviare messaggi testuali in tempo reale a un client connesso (https://www.aspitalia.com/script/1356/Messaggi-Tempo-Reale-Blazor-Server.aspx).

Usando un po' di creatività, possiamo usare questa stessa tecnica per animare degli elementi presenti nella pagina.
"Animare" vuol dire aggiornare periodicamente le proprietà dell'elemento come la posizione, il colore o la dimensione per rappresentare un'informazione che cambia nel tempo. L'aggiornamento può avvenire al verificarsi di un cambio di stato oppure a cadenza regolare, una o più volte al secondo.

Usando la grafica e le animazioni, aumentiamo il potere informativo di un pannello di monitoraggio. In questo modo possiamo realizzare dei veri e propri quadri sinottici che mostrano in tempo reale lo stato di una linea produttiva, di una rete di trasporti, o in generale, di un qualsiasi sistema che ha bisogno di essere monitorato.

Posizionare un elemento HTML


Per prima cosa, cerchiamo di capire come posizionare un elemento HTML nella pagina. Una prima soluzione consiste nell'impostare le sue proprietà CSS left e top. Blazor Server non ci permette modificare questi valori individualmente ma possiamo pur sempre agire sull'attributo style dell'elemento HTML così da impostare più di una proprietà CSS alla volta. Vediamo un esempio di un Razor Component minimale che dimostra questa tecnica:

@page "/"
<h1>Translate demo</h1>
<div class="container">
    <!-- L'attributo style dell'elemento è legato a un campo privato, in modo che lo si possa aggiornare -->
    <div style="@style"></div>
</div>
<button @onclick="TranslateElement">Translate</button>
<style>
    .container { position: relative;}
    .container div { position: absolute; width: 100px; height: 100px; background-color: red; top:0; left: 0; }
</style>
@code {
    private int left = 0;
    private int top = 0;
    private string style = "";

    private void TranslateElement()
    {
        left += 2;
        top += 1;
    //Aggiorniamo le proprietà CSS top e left
        style = $"top: {top}px; left: {left}px";
    }
}

Come risultato, ogni volta che l'utente clicca il bottone, l'elemento div risulterà spostarsi orizzontalmente di 2 pixel e verticalmente di 1 pixel. Il fatto che gli sia stata assegnata la proprietà CSS position: absolute ci permette di posizionarlo arbitrariamente all'interno del suo contenitore.

Un risultato migliore con gli elementi SVG

Quando si tratta di spostare degli elementi nella pagina, dovremmo preferire grafica SVG al posto di elementi HTML perché ci dà alcuni vantaggi:

  • Un disegno SVG ha un proprio sistema di coordinate, indipendente dal numero di pixel di cui è composta la pagina;
  • Supporta pan e zoom, il che ci permette di personalizzare l'inquadratura del disegno;
  • Gli elementi SVG possiedono appositi attributi x e y sui cui possiamo agire individualmente con Blazor Server.

Rivisitiamo quindi il precedente esempio usando elementi SVG. Notare come la sintassi risulti semplificata e perciò più chiara.

@page "/counter"
<h1>Translate demo</h1>
<svg width="100" height="100" viewBox="0 0 500 500">
    <rect x="@x" y="@y" width="100" height="100" fill="red"></rect>
</svg>
<button @onclick="TranslateElement">Translate</button>
@code {
  private int x = 0;
  private int y = 0;
  private void TranslateElement()
  {
    //Dobbiamo semplicemente aggiornare i valori di x e y
    x += 2;
    y += 1;
  }
}

Gli attributi width e height dell'elemento svg controllano la dimensione in pixel che il disegno SVG occupa all'interno della pagina, mentre invece l'attributo viewBox controlla il rettangolo dell'inquadratura, cioè cosa vogliamo che l'utente veda dell'intero disegno.

Animare la posizione degli elementi SVG

Ora che sappiamo come spostare un elemento, possiamo animare gli attributi x e y al verificarsi di determinati eventi, anziché richiedere l'interazione dell'utente. Supponiamo di dover mostrare la posizione di alcuni treni su una linea ferroviaria: iniziamo predisponendo un servizio che ci notifichi almeno il nome del treno, la sua posizione lungo la linea ferroviaria e la direzione verso cui viaggia.
Perciò riprendiamo dal precedente script e modifichiamo l'interfaccia IMessageNotifier in questo modo:

public interface IMessageNotifier
{
  event EventHandler<TrainPosition> TrainMoved;
}

L'evento TrainMoved prevede un argomento di tipo complesso TrainPosition che è in grado di fornirci tutte le informazioni necessarie. Definiamolo come segue.

public class TrainPosition
{
  //Nome del treno
  public string Name { get; set; }
  //Posizione lungo la linea ferroviaria (in questo esempio, un valore da 0 a 100)
  public float Progress { get; set; }
  //Direzione di viaggio (un'enum con i valori NorthWest e SouthEast)
  public Direction Direction { get; set; }
}

Da un Razor Component sottoscriviamo tali eventi, così che possiamo generare dinamicamente un elemento SVG per rappresentare ogni treno e aggiornare la posizione nella pagina.

@page "/"
@inject IMessageNotifier notifier
@implements IDisposable
<h1>Train demo</h1>
<svg width="1200" height="200" viewBox="-5 -5 120 20">
  <image x="-5" y="-5" width="120" height="20" href="/images/map.svg" />
  @foreach(var trainPosition in trainPositions.Values)
  {
    //Usiamo un foreach per rappresentare ogni treno: determiniamo la posizione di ciascuno
    string x = trainPosition.Position.ToString(CultureInfo.InvariantCulture);
  //Spostiamolo in alto o in basso a seconda della direzione
    string y = (trainPosition.Direction == Direction.SouthEast ? -2 : 8).ToString();
  //Aggiungiamo un tooltip per far capire all'utente di che treno si tratta
    string title = trainPosition.Name;
    //E la impostiamo su un elemento image che raffigura l'icona di un treno
    <image x="@x" y="@y" href="/images/train.svg" width="14">
      <title>@title</title>
    </image>
  }
</svg>
@code {
  //Usiamo un dizionario per conservare l'elenco unico di treni: la chiave è il nome del treno
  Dictionary<string, TrainPosition> trainPositions = new Dictionary<string, TrainPosition>();

  protected override void OnInitialized()
  {
    //Ad inizializzazione avvenuta, sottoscriviamo la ricezione di eventi
    notifier.TrainMoved += UpdateTrainPosition;
  }
  private void UpdateTrainPosition(object sender, TrainPosition position)
  {
    //Usiamo InvokeAsync per far eseguire questo codice
    //al thread del renderer, altrimenti avremmo un'eccezione
    InvokeAsync(() => 
    {
      //Aggiorniamo la posizione del treno
      trainPositions[position.Name] = position;
      StateHasChanged();
    });
  }
  public void Dispose()
  {
    //Rimuoviamo la sottoscrizione quando
    //il Razor Component non serve più
    notifier.TrainMoved -= UpdateTrainPosition;
  }
}

Blazor Server è abbastanza efficiente nell'aggiornare gli attributi degli elementi. Infatti, se andiamo negli strumenti di sviluppo del browser, noteremo che ogni aggiornamento richiede il trasferimento di poche centinaia di byte via websocket. In questo modo possiamo permetterci di aggiornare la posizione svariate volte al secondo, se necessario.


L'applicazione dimostrativa è disponibile a questo indirizzo: https://github.com/aspitalia/blazorserver-realtime-animation

Commenti

Visualizza/aggiungi commenti

| Condividi su: Twitter, Facebook, LinkedIn

Per inserire un commento, devi avere un account.

Fai il login e torna a questa pagina, oppure registrati alla nostra community.

Approfondimenti

I più letti di oggi