Lovelace con effetto WOW; animiamo le Card con CSS

HassioHelp

Lovelace con effetto WOW; animiamo le Card con CSS

.

Argomento: Lovelace

Livello: Pro (Novizio,Esperto, Pro)

Difficoltà: Alta (Bassa, Media, Alta)

Premessa

State cercando di dare un qualcosa in più alle vostre card su Home Assistant? Vi sembrano piatte e noiose? Volete che cambiono in base allo stato delle entità? La soluzione sta nell’utilizzo del codice CSS.

Cos'è il CSS?

La sigla CSS sta per Cascading Style Sheets, in italiano fogli di stile. Essenzialmente, il CSS è uno strumento di formattazione delle pagine web che consente di personalizzare l’aspetto della pagina senza agire direttamente sul codice HTML della stessa.

Grazie ad essa è possibile:

  • definire i margini del documento;
  • definire lo stile dell testo;
  • dare l’allineamento al testo;
  • gestire lo sfondo della pagina o di singoli elementi;
  • gestire elementi testuali e d’immagine;
  • creare effetti di transizione;
  • rendere dinamici i link testuali;
  • e molto altro.

Card in Lovelace

Attenzione: perché gli effetti vengano visualizzati in Home Assistant è necessario installare Card-Mod da Hacs

Vediamo subito come creare una card con alcuni effetti CSS. La card che più si appresta a questo utilizzo è decisamente la Picture-element dato che può racchiudere al suo interno più immagini.
Per questo esempio prenderò lo stato del microonde collegato ad una presa comandata la quale ne misura il consumo in questo modo posso sapere se è in funzione o meno, con un package dedicato ho creato un sensore sensor.stato_microonde che ha i seguenti stati:

  • spento (micoonde_close.png)
  • cottura (microonde_cooking.png)
  • finito (micoonde_open.png)

Lo stato spento sarà quando non rilevo nessun consumo, 
lo stato cottura sarà quando rilevo un consumo superiore hai 5W, 
lo stato finito sarà quando rileverò un consumo al di sotto dei 5W.

Ad ogni stato assocerò un’immagine diversa le quale ne determinerà visibilmente lo stato, successivamente con i fogli di stile CSS andrò ad animarne alcuni.

In questo link troverete le immagini della card da inserire nella cartella config\www\lovelace inoltre troverete il codice completo ma vi consiglio di procedere a step come viene spiegato successivamente.

Fase 1

Per creare queste card preferisco usare lovelace in modalità grafica dato che ho subito un riscontro del posizionamento degli oggetti nella card.

Da Lovelace aggiungiamo una nuova card Elementi Immagine o Picture-element, per i nostalgici e andiamo ad incollare il seguente codice.

elements:
- entity: switch.microonde
image: /local/lovelace/microonde_close.png
state_filter:
'off': brightness(50%) saturate(0.5)
'on': null
style:
left: 13.5%
top: 23%
width: 27%
transform: 'translate(0%,0%)'
tap_action:
action: toggle
type: image
image: /local/lovelace/microonde_sfondo.png
type: picture-elements

Descrizione:

  • nella card picture elements abbiamo impostato lo sfondo su image (linea 15);
  •  lo stato dello switch.microonde è stato associato all’immagine microonde_close.png (linee 2-3);
  • in state_filter (linee 4-6) sono stati inseriti dei filtri all’immagine: quando lo stato dello switch è su off rendiamo l’immagine scura (brightness) e poco colorata (saturate) mentre quando è on non aggiungiamo nulla;
  • in style definiamo con width la dimensione dell’immagine microonde_close.png, e la sua posizione con left e top.
  • aggiungiamo su transform: translate con valori 0 questo ci servirà per mantenere la posizione quando andremo ad inserire gli stili CSS, dato che ne spostano il centro dell’oggetto;
  • su tap_action aggiungiamo l’opzione toggle in questo modo quando cliccheremo sull’immagine accenderemo o spegneremo la presa comandata

Fase 2

Ora dovremmo aggiungere la seconda immagine (microonde_open.png) sopra la precedente ed associarla al sensor.stato_microonde. Aggiungiamo il seguente codice sotto la linea 14 dopo type: image che è la conclusione del codice per l’immagine precedente.

  - entity: sensor.stato_microonde
image: /local/lovelace/microonde_open.png
state_filter:
Spento: opacity(0.0)
Cottura: opacity(0.0)
Finito: null
style:
left: 3%
top: 25%
width: 32%
transform: 'translate(0%,0%)'
tap_action:
action: call-service
service: switch.toggle
service_data:
entity_id: switch.microonde
type: image

Descrizione:

Il sensor.stato_microonde come detto in precedenza ha tre stati, questa immagine la faremo comparire solamente allo stato Finito.

  • in state_filter (linee 17-20) lo stato Spento e Cottura è stato impostato il filtro opacity(0.0) quindi totalmente trasparente, mentre lo stato Finito è impostato su null quindi l’immagine tale e quale all’originale;
  • in style definiamo con width la dimensione dell’immagine microonde_open.png, e la sua posizione con left top.
  • aggiungiamo su transform: translate con valori 0 questo ci servirà per mantenere la posizione quando andremo ad inserire gli stili CSS;
  • su tap_action diversamente dal precedente dobbiamo interagire non con il sensore stato.microonde ma con lo switch.microonde. Questo perché questa card funziona a livelli; l’immagine dichiarata per ultima sovrasterà le altre sia a livello visivo ma anche a livello di interattività. Per quanto riguarda l’aspetto visivo, con l’aggiunta di uno stile di opacità, lo possiamo risolvere, invece a livello si interattività va aggiunta una chiamata switch.toggle all’entità switch.microonde (linee 26-30) in questo modo quando clickeremo sull’immagine accenderemo o spegneremo la presa comandata.

Fase 3

Ci manca ora di associare lo stato Cottura all’immagine del cursore che gira sopra il microonde chiuso. Aggiungiamo quindi il seguente codice sotto la linea 31

  - entity: sensor.stato_microonde
image: /local/lovelace/microonde_cooking.png
state_filter:
Spento: opacity(0.0)
Cottura: null
Finito: opacity(0.0)
style:
left: 18%
top: 27%
width: 12%
transform: 'translate(0%,0%)'
animation: var(--my-rotate)
tap_action:
action: call-service
service: switch.toggle
service_data:
entity_id: switch.microonde
type: image

Descrizione:

La descrizione dei comandi sono esattamente come per la precedente salvo lo state_filter che sarà null sulla fase Cottura mentre per le altre trasparenti.

Il passo successivo sarà da inserire gli effetti CSS nell’immagine ho lasciato uno spazio nelle righe 43 e 51 che verranno usate dopo per inserire il codice.

CSS

Inizieremo con un codice abbastanza semplice per far ruotare di 180° l’immagine microonde_cooking.png dando così un senso di “state in progress” l’effetto sarà il seguente

Nella riga 43 sulla sezione style aggiungiamo:

  animation: var(--my-rotate)

questa riga di codice non fa altro che richiamare l’animazione rotate che successivamente andremo a creare, quindi la dicitura generica sarebbe var(–my-ilnomedellamiaanimazione)

 

Nella riga 51 andremo ad inserire il codice per la vera e propria animazione la quale si divide in 2 parti:

la prima sarà la dichiarazione del processo di animazione con una serie di stili/filtri/trasformazioni da applicare all’immagine per ogni step dell’animazione;

la seconda dichiarazione sarà di quando verrà applicata l’animazione ( quando lo stato del sensore sensor.stato_microonde sarà su “Cottura”) e la velocità con cui verrà applicata

style: |
@keyframes rotate {
0% { transform: rotate(0deg); }
25% { transform: rotate(90deg); }
50% { transform: rotate(180deg); }
75% { transform: rotate(270deg); }
100% { transform: rotate(360deg); }
}
ha-card {
--my-rotate: {% if is_state('sensor.stato_microonde', 'Cottura') %} rotate 0.5s infinite {% endif %};
}

DESCRIZIONE:

Nella riga 52 dopo keyframes viene dichiarato il nome dell’animazione che sarà la stessa che abbiamo richiamato nella riga 43, poi vengono dichiarati gli step dell’animazione in questo caso per renderla più fluida ogni 25% della transizione viene eseguito una rotazione di 90° dell’immagine (vedremo poi altri esempi sia per la dichiarazione degli step sia delle varie trasformazioni).

Nella riga 60 viene inserita una condizione nell’esecuzione dell’animazione rotate
if is_state(‘sensor.stato_microonde’, ‘Cottura’) %} rotate 0.5s infinite {% endif %}
In pratica quando lo stato del sensore stato microonde è su Cottura esegui l’animazione rotate alla velocità di 0.5 secondi per infinito volte.

Ovviamente possiamo aggiungere quanti più animazioni vogliamo per applicarle ai vari oggetti nella card.

 

  @keyframes wiggle {
0%, 100% { transform: translate(0, 0) rotate(0); }
25% { transform: translate(-1.5px, 1.5px) rotate(0.5deg); }
50% { transform: translate(-0.5px, -0.5px) rotate(0.5deg); }
}
@keyframes smoke {
100% {
transform: translatey(-180px) scaley(1.5) scalex(1.5);
filter: blur(15px);
}
0% {
transform: translatey(0) scaley(0.5) scalex(0.5);
filter: blur(5px);
}
}
@keyframes cook {
from { filter: saturate(1); }
to { filter: saturate(0); }
}

ha-card {
--my-wiggle: {% if is_state('sensor.stato_lavatrice', 'Lavaggio') %} wiggle 75ms infinite {% endif %};
--my-cook: {% if is_state('sensor.stato_forno', 'Cottura') %} cook 1800ms infinite {% endif %};
--my-smoke: {% if is_state('sensor.stato_forno', 'Finito') %} smoke 2.5s infinite {% endif %};
}

Esisto altri attributi da aggiungere al ciclo di animazione oltre ad infinite (ripeti all’infinito il ciclo) troviamo:

  • ease-in-out: attenua il movimento all’inizio e alla fine dell’animazione, rendendo più fluido il movimento.
  • alternate: ripete l’animazione una volta da 0% a 100% ed un’altra da 100% a 0%, nelle rotazioni ad esempio una volta a destra ed una sinistra
  • reverse: l’animazione viene riprodotta in senso inverso
  • backwards:  l’oggetto prenderà i valori di stile impostati dal primo fotogramma chiave (dipende dalla direzione dell’animazione) e li manterrà durante il periodo di ritardo dell’animazione
  • both: l’animazione seguirà le regole sia in avanti che all’indietro, estendendo le proprietà dell’animazione in entrambe le direzioni

Esempi

Di seguito riporto alcuni esempi di animazioni, riposto solo la parte di codice di style e non introduco nessuna condizione nell’esecuzione dell’animazione. 

style: |
@keyframes scale-down-center {
0% {
transform: scale(1);
}
100% {
transform: scale(0.5);
}

}
ha-card {
--my-scale-down-center: scale-down-center 1.5s infinite;
}
style: |
@keyframes rotate-diagonal-bl {
0% {
transform: rotate3d(1, 1, 0, 0deg);
transform-origin: 0% 100%;
}
50% {
transform: rotate3d(1, 1, 0, 180deg);
transform-origin: 0% 100%;
}
100% {
transform: rotate3d(1, 1, 0, 360deg);
transform-origin: 0% 100%;
}
}
ha-card {
--my-rotate-diagonal-bl: rotate-diagonal-bl 1.5s infinite;
}
style: |
  @keyframes flip-horizontal-bottom {
    0% {
      transform: rotatex(0);
    }
    100% {
      transform: rotatex(-180deg);
    }
  }
  ha-card {
    --my-flip-horizontal-bottom: flip-horizontal-bottom 1.5s infinite;
  }
style: |
  @keyframes scale-up-center {
    0% {
      transform: scale(0.5);
    }
    100% {
      transform: scale(1);
  }
  }
  ha-card {
    --my-scale-up-center: scale-up-center 1.5s infinite;
  }
style: |
  @keyframes rotate-scale-up {
    0% {
      transform: scale(1) rotateZ(0);
    }
    50% {
      transform: scale(2) rotateZ(180deg);
    }
    100% {
      transform: scale(1) rotateZ(360deg);
  }

  }
  ha-card {
    --my-rotate-scale-up: rotate-scale-up 1.5s infinite;
  }
style: |
  @keyframes flip-2-ver-left-bck {
    0%  {
      transform: translateX(0) translateZ(0) rotateY(0);
      transform-origin: 0% 50%;
    }
    50% {
      transform: translateX(-100%) translateZ(-260px) rotateY(-180deg);
      transform-origin: 100% 0%;
    }
  }
  ha-card {
    --my-flip-2-ver-left-bck: flip-2-ver-left-bck 1.5s infinite;
  }
style: |
  @keyframes slide-right {
    0% {
      transform: translatex(0);
    }
    100% {
      transform: translatex(330%);
    }
  }
  ha-card {
    --my-slide-right: slide-right 2.5s infinite;
  }
style: |
  @keyframes slide-rotate-hor-top {
    0% {
      transform: translatey(0) rotatex(0deg);
    }
    100% {
      transform: translatey(-150px) rotatex(-90deg);
    }
  }
  ha-card {
    --my-slide-rotate-hor-top: slide-rotate-hor-top 1.5s infinite;
  }
style: |
  @keyframes bounce-in-top {
    0% {
      transform: translatey(-500px);
      animation-timing-function: ease-in;
      opacity: 0;
    }
    38% {
      transform: translatey(0);
      animation-timing-function: ease-out;
      opacity: 1;
    }
    55% {
      transform: translatey(-65px);
      animation-timing-function: ease-in;
    }
    72% {
      transform: translatey(0);
      animation-timing-function: ease-out;
    }
    81% {
      transform: translatey(-28px);
      animation-timing-function: ease-in;
    }
    90% {
      transform: translatey(0);
      animation-timing-function: ease-out;
    }
    95% {
      transform: translatey(-8px);
      animation-timing-function: ease-in;
    }
    100% {
      transform: translatey(0);
      animation-timing-function: ease-out;
    }
  }
  ha-card {
    --my-bounce-in-top: bounce-in-top 1.5s infinite;
  }
style: |
  @keyframes roll-in-left {
    0% {
      transform: translatex(-300px) rotate(-540deg);
      opacity: 0;
    }
    100% {
      transform: translatex(0) rotate(0deg);
      opacity: 1;
    }
  }
  ha-card {
    --my-roll-in-left: roll-in-left 1.5s infinite;
  }
style: |
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
ha-card {
--my-fade-in: fade-in 2s infinite;
}
style: |
  @keyframes puff-in-center {
    0% {
      transform: scale(2);
      filter: blur(4px);
      opacity: 0;
    }
    100% {
      transform: scale(1);
      filter: blur(0px);
      opacity: 1;
    }
  }
  ha-card {
    --my-puff-in-center: puff-in-center 2s infinite;
  }
style: |
@keyframes puff-in-hor {
0% {
transform: scalex(2);
filter: blur(4px);
opacity: 0;
}
100% {
transform: scalex(1);
filter: blur(0px);
opacity: 1;
}
}
ha-card {
--my-puff-in-hor: puff-in-hor 2s infinite;
}
style: |
  @keyframes vibrate-1 {
    0% {
      transform: translate(0);
    }
    20% {
      transform: translate(-2px, 2px);
    }
    40% {
      transform: translate(-2px, -2px);
    }
    60% {
      transform: translate(2px, 2px);
    }
    80% {
      transform: translate(2px, -2px);
    }
    100% {
      transform: translate(0);
    }
  }
  ha-card {
    --my-vibrate-1: vibrate-1 300ms infinite;
  }
style: |
  @keyframes flicker-1 {
    0%,
    100% {
      opacity: 1;
    }
    41.99% {
      opacity: 1;
    }
    42% {
      opacity: 0;
    }
    43% {
      opacity: 0;
    }
    43.01% {
      opacity: 1;
    }
    47.99% {
      opacity: 1;
    }
    48% {
      opacity: 0;
    }
    49% {
      opacity: 0;
    }
    49.01% {
      opacity: 1;
    }
  }
  ha-card {
    --my-flicker-1 : flicker-1 1s infinite;
  }
style: |
  @keyframes wobble-hor-bottom {
    0%,
    100% {
      transform: translateX(0%);
      transform-origin: 50% 50%;
    }
    15% {
      transform: translatex(-30px) rotate(-6deg);
    }
    30% {
      transform: translatex(15px) rotate(6deg);
    }
    45% {
      transform: translatex(-15px) rotate(-3.6deg);
    }
    60% {
      transform: translatex(9px) rotate(2.4deg);
    }
    75% {
      transform: translatex(-6px) rotate(-1.2deg);
    }
  }
  ha-card {
    --my-wobble-hor-bottom: wobble-hor-bottom 2s infinite;
  }
style: |
@keyframes heartbeat {
from {
transform: scale(1);
transform-origin: center center;
animation-timing-function: ease-out;
}
10% {
transform: scale(0.91);
animation-timing-function: ease-in;
}
17% {
transform: scale(0.98);
animation-timing-function: ease-out;
}
33% {
transform: scale(0.87);
animation-timing-function: ease-in;
}
45% {
transform: scale(1);
animation-timing-function: ease-out;
}
}
ha-card {
--my-heartbeat: heartbeat 1.5s infinite;
}
  @keyframes smoke {
    100% {
      transform: translatey(-200px) scaley(1.5) scalex(1.5);
      filter: blur(15px);
  }
    0% {
      transform: translatey(0) scaley(0.5) scalex(0.5);
      filter: blur(5px);
    }
  }
  ha-card {
    --my-smoke: smoke 2.5s infinite;
  }

Conclusioni

Come avrete visto le possibili varianti sono infinite, il mondo degli effetti ottenibili con CSS sono molteplici e si possono applicare a molte delle card che normalmente usiamo, quindi …all’opera!!

 

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *