Skip to content

Old static site I made with Threejs in the past to sell T-Shirt designs through BigCartel's backend. Optimization through Webpack 5; webp images, draco compression for .gltf models minification etc...

License

Notifications You must be signed in to change notification settings

ethanny2/cyberpunk-ecommerce-frontend

Repository files navigation

GitHub issuesGitHub forksGitHub starsGitHub licenseTwitter Badge

Cyberpunk Themed eCommerce store

Demo gif

Background

The original concept for the site was to sell clothing for my short-lived clothing brand "else-if" clothing. The site was originally hosted on Big Cartel and was made using their custom platform which abstracted the product backend and gave developers a nice interface to interact with the backend through templating and ruby + Coffeescript.

This site is meant to emulate the 80s / 90s cyberpunk aesthetic commonly seen in pop culture by having the UI look like a CRT monitor with scanlines, text being animated like a command prompt etc...

Goals (for rehosting site) :

Technology used

  • SCSS
  • webpack 5
  • Stripe Checkout
  • Threejs
  • Draco Compression of GLTF models
  • Image optimizations with webp + fallbacks
  • Intersection Observer
  • webgl lens flare effect
  • Client side cart system with persistence through localStorage

Concepts

Threejs + Draco Compression

Integrating the Three.js webgl library with webpack 5 to display the graphic on the home page. I applied Draco compression to my .gltf model to drastically reduce the size of the models and thus the websites bundle size. Models were converted to draco compressed gltfs via the gltf-pipeline CLI.

const dracoDecodePath = "https://raw.githubusercontent.com/mrdoob/three.js/dev/examples/js/libs/draco/";
const loader = new GLTFLoader(manager);
const dracoLoader = new DRACOLoader(manager);
dracoLoader.setDecoderPath(dracoDecodePath);
dracoLoader.setDecoderConfig({ type: "js" });
dracoLoader.preload();
loader.setDRACOLoader(dracoLoader);

Stripe Checkout + Local Storage Cart

Since the Big Cartel backend attached to this site is no longer active I opted to have the entire store operate on the client side with checkout via the Stripe client side library.

checkout.addEventListener("click", async () => {
    const lineItems = fetchCart().products.map((item) => ({ price: String(item.productId), quantity: item.quantity }));
    await stripe.redirectToCheckout({
      mode: "payment",
      lineItems,
      successUrl: "https://elseifclothing.netlify.app/success",
      cancelUrl: "https://elseifclothing.netlify.app/failure"
    });
  });

The cart is a quick implementation written on the client side with persistence.

...
export function fetchCart() {
  return JSON.parse(localStorage.getItem(CART_KEY)) || { products: [] };
}
export function updateCartDisplay() {
  document.getElementById("lblCartCount").textContent = getTotalItems() || "";
}

export function addToCart(newProduct) {
  const cart = fetchCart();
  const foundIndex = cart.products.findIndex(
    (product) => product.name === newProduct.name && product.size === newProduct.size
  );
  if (foundIndex !== -1) {
    cart.products[foundIndex].quantity++;
  } else {
    newProduct.quantity = 1;
    cart.products.push(newProduct);
  }
  updateCart(cart);
}
...

Intersection Observer API (w/ Polyfill)

Implementing the polyfill version (so I can test on my older phone) of the Intersection Observer API so that when certain text is scrolled into the viewport a terminal/ command prompt effect types out the text.

function createObserver() {
  const cmd = document.getElementsByClassName("typing")[0];
  let observer;
  let options = {
    root: null,
    rootMargin: "0px",
    threshold: [0.5]
  };
  observer = new IntersectionObserver(handleIntersect, options);
  observer.observe(cmd);
}

function handleIntersect(entries, observer) {
  const cmd = document.getElementsByClassName("typing")[0];
  entries.forEach((entry) => {
    if (entry.intersectionRatio > 0.5) {
      handleCommandLineMessage(introMessage);
      // Only happens once
      observer.unobserve(cmd);
    }
  });
}

webpack 5 bundling

Using my own custom webpack 5 dev and production configuration to have a local dev-server with hot module replacement and optimizied production build with minification, auto-prefixing for CSS properties and more. The upgrade from webpack 4 -> webpack 5 makes bundling static assets (images, json files etc...) very easy.

/* loads nearly all assets; no external plugins */
{
        test: /\.(jpg|JPG|jpeg|png|gif|mp3|svg|ttf|webp|woff2|woff|eot)$/i,
        type: "asset/resource"
},

webp images with fallback

Running a script as a pre-build step convert all png/jpg files to webp versions to cut back on bundle sizes for browsers that do support webp images.

(async () => {
  const img = await imagemin([path.resolve(__dirname, "src/static/images/*.{jpg,png}").replace(/\\/g, "/")], {
    destination: path.resolve(__dirname, "src/static/images/").replace(/\\/g, "/"),
    plugins: [imageminWebp({ quality: 70 })]
  });
  console.log(img);
  console.log("Done converting images");
})();

About

Old static site I made with Threejs in the past to sell T-Shirt designs through BigCartel's backend. Optimization through Webpack 5; webp images, draco compression for .gltf models minification etc...

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published