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...
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) :
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);
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);
}
...
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);
}
});
}
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"
},
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");
})();