Exiros

The Presentation

Heey, I'm Agus

  • I like most working in UI
  • I've been working here for a year and a half
  • TS is my nemesis
  • I get reaaally nervous when I have to talk to a lot of people

Things I learned working in a recycled website

  • No, most of the work it's not already done.
  • It's better to start over that trying to adapt the new design with lots of  hacks.
  • You don't necessary have to use the same old implementations.

Slider with Tiny-slider + css

import AbstractBlock from 'app/component/block/AbstractBlock';

export default class C01SliderHero extends AbstractBlock {
  public static displayName: string = 'c01-slider-hero';
  constructor(el: any) {
    super(el);
    //@ts-ignore
    const slider = tns({
      container: '[data-tiny-slider]',
      mode: 'gallery',
      animateIn: 'transition-in',
      animateOut: 'transition-out',
      items: 1,
      slideBy: 'page',
      autoplay: true,
      arrowKeys: true,
      controlsPosition: 'bottom',
      controlsContainer: '.controls-container',
      navContainer: '.nav-container',
      autoplayButtonOutput: false,
      speed: 1000,
    });
    const buttons = this.getElements('[data-control]');
    buttons.forEach(el => el.addEventListener('click', this.stopAnim));
  }

  stopAnim = () => {
    this.element.classList.add('no-anim');
  };

  public dispose() {
    super.dispose();
    const buttons = this.getElements('[data-control]');
    buttons.forEach(el => el.removeEventListener('click', this.stopAnim));
  }
}

Component updates

  • Accordion
import AbstractBlock from '../AbstractBlock';

export default class C10Accordion extends AbstractBlock {
  public static readonly displayName: string = 'c10-accordion';

  private readonly accordionButtons: ReadonlyArray<HTMLDivElement> = this.getElements(
    '[data-accordion-button]',
  );
  private readonly accordionPanels: ReadonlyArray<HTMLDivElement> = this.getElements(
    '[data-accordion-panel]',
  );

  private readonly accordionTitles: ReadonlyArray<HTMLDivElement> = this.getElements(
    '[data-accordion-title]',
  );
  private readonly accordionItems: ReadonlyArray<HTMLDivElement> = this.getElements(
    '[data-accordion-content]',
  );

  private prevIndex: number;

  private accordionButtonListeners: Array<EventListener> = [];

  constructor(el: HTMLElement) {
    super(el);

    this.prevIndex = 0;

    this.displayFirstItem();

    this.addEventListeners();
  }

  private toggleDisplay = (index: number) => {
    const clickedButton = this.accordionButtons[index];
    const clickedPanel = this.accordionPanels[index];

    clickedButton.classList.toggle('active');
    clickedPanel.style.display === 'block'
      ? (clickedPanel.style.display = 'none')
      : (clickedPanel.style.display = 'block');
  };

  private displayFirstItem = () => {
    const firstItem = this.accordionItems[0];

    firstItem.classList.toggle('active');
  };

  private toggleItem = (index: number) => {
    const prevItem = this.accordionItems[this.prevIndex];
    const clickedItem = this.accordionItems[index];

    prevItem.classList.toggle('active');
    clickedItem.classList.toggle('active');

    this.prevIndex = index;
  };

  private addEventListeners(): void {
    this.accordionButtons.forEach((element: HTMLElement, index: number) => {
      this.addListener(element, 'click', () => this.toggleDisplay(index));
    });
    this.accordionTitles.forEach((element: HTMLElement, index: number) => {
      this.addListener(element, 'click', () => this.toggleItem(index));
    });
  }

  public dispose() {
    super.dispose();
  }
}

https://codepen.io/seedoflife/pen/MRMWxd?css-preprocessor=scss

Component updates

  • Custom cursor
import AbstractComponent from '../../AbstractComponent';

import { TweenLite, Power3 } from 'gsap';
import lerp from '../../../util/lerp';
import bowser from 'bowser';

export default class M14CustomCursor extends AbstractComponent {
  public static displayName: string = 'm14-custom-cursor';

  private static readonly IS_ACTIVE: string = 'is-active';

  private blocks: Array<HTMLElement> = [];

  private isDesktop: boolean =
    (bowser.getParser(window.navigator.userAgent) as any).parsedResult.platform.type === 'desktop';
  private cursorSize: number = 0;
  private scale: number = 1;
  private isAnimating: boolean = false;
  private mousePosition: { x: number; y: number; lastX: number; lastY: number } = {
    x: 0,
    y: 0,
    lastX: 0,
    lastY: 0,
  };

  constructor(el: HTMLElement) {
    super(el);
  }

  public adopted() {
    if (this.isDesktop) {
      this.blocks = [].slice.call(document.querySelectorAll('[data-custom-cursor]'));
      this.cursorSize = this.element.clientWidth;
      this.updatePosition();
      this.addEventListeners();
    }
  }

  private handleMouseMove = (event: MouseEvent) => {
    this.mousePosition.x = event.clientX - this.cursorSize * 0.5;
    this.mousePosition.y = event.clientY + this.cursorSize * 0.25;
  };

  private handleMouseDown = () => {
    TweenLite.to(this, 0.6, { scale: 0.85, ease: Power3.easeOut });
    if (this.isAnimating) document.body.style.cursor = 'grabbing';
  };

  private handleMouseUp = () => {
    TweenLite.to(this, 0.4, { scale: 1, ease: Power3.easeOut });
    if (this.isAnimating) document.body.style.cursor = 'grab';
  };

  private updatePosition = () => {
    requestAnimationFrame(this.updatePosition);

    this.mousePosition.lastX = lerp(this.mousePosition.lastX, this.mousePosition.x, 0.1);
    this.mousePosition.lastY = lerp(this.mousePosition.lastY, this.mousePosition.y, 0.1);

    if (!this.isAnimating) return;

    this.element.style.transform = `translate3d(${this.mousePosition.lastX}px, ${
      this.mousePosition.lastY
    }px, 0) scale(${this.scale})`;
  };

  private handleMouseEnter = () => {
    this.isAnimating = true;
    document.body.style.cursor = 'grab';
    this.element.classList.add(M14CustomCursor.IS_ACTIVE);
  };

  private handleMouseLeave = () => {
    this.isAnimating = false;
    document.body.style.cursor = '';
    this.element.classList.remove(M14CustomCursor.IS_ACTIVE);
  };

  private addEventListeners = () => {
    this.blocks.forEach((element: HTMLElement) => {
      element.addEventListener('mouseenter', this.handleMouseEnter);
      element.addEventListener('mouseleave', this.handleMouseLeave);
    });

    document.addEventListener('mousemove', this.handleMouseMove);
    document.addEventListener('mousedown', this.handleMouseDown);
    document.addEventListener('mouseup', this.handleMouseUp);
  };

  private removeEventListeners = () => {
    document.removeEventListener('mousemove', this.handleMouseMove);
    document.removeEventListener('mousedown', this.handleMouseDown);
    document.removeEventListener('mouseup', this.handleMouseUp);
  };

  public dispose() {
    this.removeEventListeners();
    super.dispose();
  }
}
  • Reused carousel draggable behaviour from same library as home Slider Hero
  • Add svg in sass file
constructor(el: HTMLElement) {
    super(el);
    //@ts-ignore
    const slider = tns({
      container: '[data-tiny-slider]',
      mouseDrag: true,
      loop: false,
      fixedWidth: 240,
      controls: false,
      nav: false,
      gutter: 52,
      responsive: {
        1024: {
          fixedWidth: 305,
          gutter: 185,
        },
      },
    });

Challenges

Offset right to left / left to right

@mixin column($columnsWanted, $total: 12) {
  $columnsTotal: $total;
  $gapSize: 55;
  $percentage: (($columnsWanted / $columnsTotal) * 100) * 1%;
  $offset: ($gapSize / $columnsTotal) * ($columnsTotal - $columnsWanted) + px;
  width: 100%;
  max-width: calc(#{$percentage} - #{$offset});

  @media (max-width: 480px) {
    $gapSize: 20;
    $offset: ($gapSize / $columnsTotal) * ($columnsTotal - $columnsWanted) + px;
    max-width: calc(#{$percentage} - #{$offset});
  }
}

@mixin offsetFullWidth($columnsWanted, $total: 12, $direction) {
  /*
    README
    Works only for desktop and above (1024+) and position absolute on the element
    The numbers 20 and 13.333 are *magic* numbers found through patterns
    looking at the offsets needed for both 8 & 12 columns grid
    We could figure out where they come from but for now they just *work*
    We SHOULD reconsider how this grids are made and allow offsets like bootstrap:
    https://getbootstrap.com/docs/4.0/layout/grid/#offsetting-columns
  */
  $columnsTotal: $total;
  $magicNumber: 20;
  @if $columnsTotal == 12 {
    $magicNumber: 13.333;
  }
  $gapSize: 55;
  $containerPadding: 80;
  $percentage: (($columnsWanted / $columnsTotal) * 100) * 1%;
  $offset: ($gapSize / $columnsTotal) * ($columnsTotal - $columnsWanted) + px;
  $finalOffset: $containerPadding + ($gapSize - ($columnsWanted * $magicNumber)) + px;
  width: 100%;
  #{$direction}: calc(#{$percentage} - #{$offset} + #{$finalOffset});
}

Conclusion

deck

By agustinap91

deck

  • 99