htmlangularcordovaionic-frameworkionic7

Is there a better way to change opacity and background of button on click?


I am trying to get a screen working in Ionic/Angular where different buttons can be pressed and change their opacity until a threshold is reached and then change the color of the button. An overview is provided with the picture below:

Calibration

My current implementation is working but I think there should be a better way to achieve this because it looks kinda bad with all the if cases and I am not sure if this is the right way to access the button and edit its opacity.

My implementation:

calibration.page.ts

const BUTTONS = [
  { id: 1, opacity: 1.0 },
  { id: 2, opacity: 1.0 },
  { id: 3, opacity: 1.0 },
  { id: 4, opacity: 1.0 },
  { id: 5, opacity: 1.0 },
  { id: 6, opacity: 1.0 },
  { id: 7, opacity: 1.0 },
  { id: 8, opacity: 1.0 },
  { id: 9, opacity: 1.0 }
]

export class CalibrationPage implements OnInit {

  buttons = BUTTONS;

  @ViewChild('Pt0') button0: any;
  @ViewChild('Pt1') button1: any;
  @ViewChild('Pt2') button2: any;
  @ViewChild('Pt3') button3: any;
  @ViewChild('Pt4') button4: any;
  @ViewChild('Pt5') button5: any;
  @ViewChild('Pt6') button6: any;
  @ViewChild('Pt7') button7: any;
  @ViewChild('CalibrationPoint') buttonCalibration: any;

buttonClicked(buttonId: any) {
    if (buttonId == 0) {
      this.buttons[buttonId].opacity -= 0.2;
      if (this.buttons[buttonId].opacity > 0.1) {
        this.button0.el.style.setProperty('--opacity', this.buttons[buttonId].opacity)
      } else {
        this.button0.el.style.setProperty('--background', 'red')
        this.button0.el.style.setProperty('--opacity', '1.0')
      }
    } else if (buttonId == 1) {
      this.buttons[buttonId].opacity -= 0.2;
      if (this.buttons[buttonId].opacity > 0.1) {
        this.button1.el.style.setProperty('--opacity', this.buttons[buttonId].opacity)
      } else {
        this.button1.el.style.setProperty('--background', 'red')
        this.button1.el.style.setProperty('--opacity', '1.0')
      }
          ... and so on for all the other buttons

calibration.page.html

<ion-grid>
  <ion-row>
    <ion-col>
      <ion-button #Pt0 id="Pt0" shape="circle" size="small" (click)="buttonClicked(0)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
    <ion-col>
      <ion-button #Pt1 id="Pt1" shape="circle" size="small" (click)="buttonClicked(1)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
  </ion-row>
  <ion-row>
    <ion-col>
      <ion-button #Pt2 id="Pt2" shape="circle" size="small" (click)="buttonClicked(2)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
  </ion-row>
  <ion-row>
    <ion-col>
      <ion-button #Pt3 id="Pt3" shape="circle" size="small" (click)="buttonClicked(3)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
    <ion-col>
      <ion-button #CalibrationPoint id="CalibrationPoint" shape="circle" size="small" disabled="true" (click)="buttonClicked(8)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
    <ion-col>
      <ion-button #Pt4 id="Pt4" shape="circle" size="small" (click)="buttonClicked(4)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
  </ion-row>
  <ion-row>
    <ion-col>
      <ion-button #Pt5 id="Pt5" shape="circle" size="small" (click)="buttonClicked(5)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
  </ion-row>
  <ion-row>
    <ion-col>
      <ion-button #Pt6 id="Pt6" shape="circle" size="small" (click)="buttonClicked(6)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
    <ion-col>
      <ion-button #Pt7 id="Pt7" shape="circle" size="small" (click)="buttonClicked(7)">
        <ion-icon slot="icon-only"></ion-icon>
      </ion-button>
    </ion-col>
  </ion-row>
</ion-grid>

Can someone help me here and/or have tips to achieve what I want?


Solution

  • The first is use .css to animate the "dots". If, e.g. you define a .css like

    .dot {
      background-color: silver;
      border-radius: 50%;
      height: 1rem;
      width:1rem;
    }
    .dot.animate{
      background:red;
    
      animation:
      3s 1 alternate fade;
    }
    @keyframes fade {
      0% {
        opacity: 1;
        background:silver;
      }
      99% {
        opacity: 0;
        background:silver;
      }
      100%
      {
        opacity:1;
      }
    }
    .dot.show{
      background-color:silver;
    }
    

    We can see that the only is replace one class by another. Imagine some simple

    <div #dot class="dot show" (click)="animate(dot)"></div>
    

    And the function animate

      animate(element:HTMLElement)
      {
        element.classList.remove("show");
        element.classList.add("animate");
      }
    

    See that, in Angular, when we use a template reference variable and use in the .html we are pass the HTMLElement. Be careful!, if we use @ViewChild we get a ElementRef (and in elementRef.nativeElement is where we "reach" the HTLMElement)

    Well, I imagine you need "some more" that "animate" the buttons. So, not use ViewChild, else ViewChildren. ViewChildren is a QueryList So if we have some like

    @for(i of [1,2,3,4]; track $index)
    {
    <div  #dot class="dot show" [attr.data-index]="i" 
        (click)="animate($index)"></div> //<--I choose now pass the index
    }
    

    Our animate can be now

      //declare first a variable message and ViewChildren
      message:string=""
      @ViewChildren('dot') dots!:QueryList<ElementRef>
    
      animate(index:number)
      {
        const dot=this.dots.find((x:ElementRef,i:number)=>i==index)
        dot?.nativeElement.classList.remove("show");
        dot?.nativeElement.classList.add("animate");
        this.message="You click the "+
             dot?.nativeElement.getAttribute('data-index')+ " dot"
      }
    

    a stackblitz (it's only angular)

    Update

    To position the dots, we can use css grid (see this large article to understand it)

    .container {
      display: grid;
      grid-template-columns: repeat(2,1fr);
      grid-template-rows: repeat(7, 1fr);
      grid-auto-flow:column row;
    }
    .dot:nth-child(3n)
    {
      grid-column-start:1;
      grid-column-end:3;
      justify-self:center;
    }
    .dot:nth-child(3n-1)
    {
      justify-self:end;
    }