Renzos
dev-Blog.

I am always looking for new ways to make my life as a developer easier.

Explore Articles
HTML CSS tailwind Next.js astro Payload payload cms Official PHP Logo image/svg+xml Official PHP Logo Colin Viebrock Copyright Colin Viebrock 1997 - All rights reserved. 1997 php React Logo react sass symfony vite wordpress
HTML CSS tailwind Next.js astro Payload payload cms Official PHP Logo image/svg+xml Official PHP Logo Colin Viebrock Copyright Colin Viebrock 1997 - All rights reserved. 1997 php React Logo react sass symfony vite wordpress
Latest Article
  • HTML
  • JavaScript
  • Tailwind CSS
  • TypeScript

Embracing Object-Oriented Programming in Web Development

Written on 24. Mai, 2024 | by Renzo Smania

Foreword

Object-Oriented Programming (OOP) has long been a staple in software development due to its ability to model real-world scenarios, promote code reuse, and improve maintainability. In web development, while many tasks can be handled using functional programming paradigms, there are scenarios where OOP shines. This article explores the reasons for using OOP in web development and provides a practical example.

Why Use OOP in Web Development?

1. Modularity and Reusability

OOP allows you to break down complex problems into smaller, manageable components. These components, or objects, can be reused across different parts of your application, reducing code duplication and improving maintainability.

2. Encapsulation

Encapsulation is the practice of bundling data and methods that operate on that data within a single unit, or class. This helps to protect the internal state of an object and exposes only necessary functionalities, leading to cleaner and more secure code.

3. Inheritance

Inheritance enables new classes to inherit properties and methods from existing classes, promoting code reuse and the creation of a hierarchical class structure. This can simplify the management of related objects.

4. Polymorphism

Polymorphism allows objects to be treated as instances of their parent class rather than their actual class. This provides flexibility in how functions and methods are invoked, leading to more dynamic and scalable code.

Practical Example: Image Slider with OOP

Consider a scenario where you need to implement an image slider on your website. An image slider typically involves various interactive elements like navigation dots, arrows, and touch gestures. Using OOP can help manage these elements efficiently.

HTML Structure

<div class="mt-12"></div>
<div class="p-6">
  <div class="w-full max-w-screen-md mx-auto relative border bg-neutral-50 shadow-md">
    <div data-slider="id">
      <div data-slider-image>
        <div class="absolute bottom-0 max-h-full w-full pb-16 bg-black bg-opacity-20 text-white p-3 overflow-auto">
          Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
        </div>
        <img src="https://images.unsplash.com/photo-1715845608783-e9b51eafc3f9?q=80&w=2071&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D" alt="test">
      </div>
      <!-- Additional images here... -->
    </div>
  </div>
</div>

JavaScript with OOP

function addGlobalStyles() {
  const style = document.createElement("style");
  style.textContent = `
    :root {
      --animation-duration: 0.5s;
    }

    .animate-out-right,
    .animate-in-left,
    .animate-in-right,
    .animate-out-left {
      animation-duration: var(--animation-duration);
      animation-timing-function: ease-in-out;
    }


    @keyframes slide-out {
      from {
        transform: translateX(var(--start-translate-x, 0));
        opacity: 100%;
        scale: 1;
      }
      to {
        transform: translateX(100%);
        opacity: 0%;
        scale: 1;
      }
    }

    @keyframes slide-in {
      from {
        transform: translateX(-100%);
        opacity: 0%;
        scale: 1;
      }
      to {
        transform: translateX(var(--start-translate-x, 0));
        opacity: 100%;
        scale: 1;
      }
    }

    .animate-out-right {
      animation-name: slide-out;
    }

    .animate-in-left {
      animation-name: slide-in;
    }

    .animate-in-right {
      animation-name: slide-out;
      animation-direction: reverse;
    }

    .animate-out-left {
      animation-name: slide-in;
      animation-direction: reverse;
    }
  `;
  document.head.appendChild(style);
}

// Call the function to add the styles
addGlobalStyles();

class NavDotContainer {
  constructor(parent, parentElement) {
    this.element = document.createElement("div");
    this.parent = parent;
    this.parentElement = parentElement;
    this.navDots = [];
    this.create();
  }
  create() {
    this.element.classList.add(
      ..."absolute bottom-3 rounded-full z-10 left-1/2 -translate-x-1/2 p-2 bg-neutral-100 bg-opacity-80 flex gap-3".split(
        " "
      )
    );
    this.parentElement.append(this.element);

    console.log(this.parent.sliderElements);
    this.parent.sliderElements.forEach((sliderElement, index) => {
      const navDot = document.createElement("div");
      navDot.classList.add(
        ..."w-3 h-3 border rounded-full data-[slider-active=true]:bg-neutral-500 hover:bg-white cursor-pointer".split(
          " "
        )
      );
      navDot.addEventListener("click", () => {
        this.parent.setActive(index);
      });
      this.element.append(navDot);
      this.navDots.push(navDot);
    });
  }
}

class NavArrows {
  constructor(parent, parentElement) {
    this.parent = parent;
    this.parentElement = parentElement;
    this.create();
  }
  create() {
    const arrowLeft = document.createElement("button");
    const arrowRight = document.createElement("button");
    arrowLeft.classList.add(
      ..."absolute border hover:shadow-md bottom-3 left-3 bg-neutral-100 bg-opacity-80 cursor-pointer w-7 h-7 grid place-items-center hover:bg-opacity-100 transition-all".split(
        " "
      )
    );
    arrowLeft.addEventListener("click", () => {
      this.parent.onLeftClick();
    });
    arrowLeft.innerHTML =
      '<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M8.84182 3.13514C9.04327 3.32401 9.05348 3.64042 8.86462 3.84188L5.43521 7.49991L8.86462 11.1579C9.05348 11.3594 9.04327 11.6758 8.84182 11.8647C8.64036 12.0535 8.32394 12.0433 8.13508 11.8419L4.38508 7.84188C4.20477 7.64955 4.20477 7.35027 4.38508 7.15794L8.13508 3.15794C8.32394 2.95648 8.64036 2.94628 8.84182 3.13514Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>';
    this.parentElement.append(arrowLeft);
    arrowRight.classList.add(
      ..."absolute border hover:shadow-md bottom-3 right-3 bg-neutral-100 bg-opacity-80 cursor-pointer w-7 h-7 grid place-items-center hover:bg-opacity-100 transition-all".split(
        " "
      )
    );
    arrowRight.addEventListener("click", () => {
      this.parent.onRightClick();
    });
    arrowRight.innerHTML =
      '<svg width="15" height="15" viewBox="0 0 15 15" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M6.1584 3.13508C6.35985 2.94621 6.67627 2.95642 6.86514 3.15788L10.6151 7.15788C10.7954 7.3502 10.7954 7.64949 10.6151 7.84182L6.86514 11.8418C6.67627 12.0433 6.35985 12.0535 6.1584 11.8646C5.95694 11.6757 5.94673 11.3593 6.1356 11.1579L9.565 7.49985L6.1356 3.84182C5.94673 3.64036 5.95694 3.32394 6.1584 3.13508Z" fill="currentColor" fill-rule="evenodd" clip-rule="evenodd"></path></svg>';
    this.parentElement.append(arrowRight);
  }
}

class Slider {
  constructor(element) {
    this.element = element;
    this.id = this.element.getAttribute("data-slider");
    this.sliderElements = [];
    this.currentIndex = 0;
    this.navDotContainer;
    this.element
      .querySelectorAll("[data-slider-image]")
      .forEach((element) => this.sliderElements.push(element));
    this.touchendX = 0;
    this.touchstartX = 0;
    this.touchmoveX = 0;
    this.isDragging = false;
    this.create();
  }
  create() {
    this.navDotContainer = new NavDotContainer(this, this.element);
    this.element.classList.add(
      ..."w-full aspect-[3/2] relative overflow-x-clip".split(" ")
    );
    this.sliderElements.forEach((element) => {
      element.classList.add(
        ..."absolute top-0 left-0 w-full h-full flex justify-center opacity-0 data-[slider-active=true]:opacity-100".split(
          " "
        )
      );
      element
        .querySelector("img")
        .classList.add(..."max-h-full max-w-full object-contain".split(" "));
    });
    this.navArrows = new NavArrows(this, this.element);
    this.setActive(0);

    this.element.addEventListener(
      "touchstart",
      (event) => {
        this.touchstartX = event.changedTouches[0].screenX;
        this.isDragging = true;
      },
      false
    );
    this.element.addEventListener(
      "touchmove",
      (event) => {
        if (!this.isDragging) return;
        this.touchmoveX = event.changedTouches[0].screenX;
        const currentElement = this.sliderElements[this.currentIndex];
        const translateX = this.touchmoveX - this.touchstartX;
        currentElement.style.transform = `translateX(${translateX}px)`;
        currentElement.classList.add("dragging");
      },
      false
    );

    this.element.addEventListener(
      "touchend",
      (event) => {
        this.isDragging = false;
        this.touchendX = event.changedTouches[0].screenX;
        const swipeDistance = this.touchendX - this.touchstartX;
        const swipeThreshold = 50; // Minimum distance to be considered a swipe
        const currentElement = this.sliderElements[this.currentIndex];
        const currentTranslateX = this.touchmoveX - this.touchstartX;

        currentElement.style.setProperty(
          "--start-translate-x",
          `${currentTranslateX}px`
        );

        if (Math.abs(swipeDistance) > swipeThreshold) {
          if (swipeDistance < 0) {
            this.onRightClick(true);
          } else {
            this.onLeftClick(true);
          }
        } else {
          currentElement.style.transform = `translateX(0)`;
        }

        currentElement.classList.remove("dragging");
      },
      false
    );
  }
  setActive(index) {
    this.navDotContainer.navDots.forEach((navDot) => {
      navDot.removeAttribute("data-slider-active");
    });
    Array.from(this.sliderElements).forEach((sliderElement) => {
      sliderElement.removeAttribute("data-slider-active");
    });
    this.navDotContainer.navDots[index].setAttribute(
      "data-slider-active",
      "true"
    );
    this.sliderElements[index].setAttribute("data-slider-active", "true");
    this.currentIndex = index;
  }
  handleAnimation(element, animationClass) {
    element.classList.add(animationClass);
    const handleAnimationEnd = () => {
      element.classList.remove(animationClass);
      element.removeEventListener("animationend", handleAnimationEnd);
      element.style.removeProperty("--start-translate-x"); // Remove the property after animation
      element.style.transform = `translateX(0)`;
    };
    element.addEventListener("animationend", handleAnimationEnd);
  }

  onLeftClick(isTouch) {
    const prevElement = this.sliderElements[this.currentIndex];
    this.handleAnimation(prevElement, "animate-out-right");
    !isTouch ? prevElement.style.setProperty("--start-translate-x", "0%") : "";

    this.currentIndex =
      (this.currentIndex - 1 + this.sliderElements.length) %
      this.sliderElements.length;
    this.setActive(this.currentIndex);
    const newElement = this.sliderElements[this.currentIndex];
    newElement.style.setProperty("--start-translate-x", "0%");
    this.handleAnimation(newElement, "animate-in-left");
  }

  onRightClick(isTouch) {
    const prevElement = this.sliderElements[this.currentIndex];
    this.handleAnimation(prevElement, "animate-out-left");
    !isTouch ? prevElement.style.setProperty("--start-translate-x", "0%") : "";
    this.currentIndex = (this.currentIndex + 1) % this.sliderElements.length;
    this.setActive(this.currentIndex);
    const newElement = this.sliderElements[this.currentIndex];
    newElement.style.setProperty("--start-translate-x", "0%");
    this.handleAnimation(newElement, "animate-in-right");
  }
  handleSwipe() {
    const swipeDistance = this.touchendX - this.touchstartX;
    const swipeThreshold = 50; // Minimum distance to be considered a swipe

    if (Math.abs(swipeDistance) > swipeThreshold) {
      if (swipeDistance < 0) {
        this.onRightClick();
      } else {
        this.onLeftClick();
      }
    }
  }
}

const sliders = document.querySelectorAll("[data-slider]");

sliders.forEach((entry) => {
  const slider = new Slider(entry);
});

In Action

Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum.
test
test
test
test
test
test
test