<!--
Made with LUME.
http://github.com/lume/lume
-->
<script src="https://assets.codepen.io/191583/LUME-5f4f19fa4d6908f1b6d74532ddb2554582e7e86e.js"></script>
<!-- Polyfill for Pointer Events (boo Safari) -->
<script src="https://code.jquery.com/pep/0.4.3/pep.js"></script>
<!-- By default a <lume-scene> fills the space of it's parent, in this case the <body>. -->
<lume-scene experimental-webgl touch-action="none">
<!-- Add some light to the scene! -->
<lume-point-light size="0 0" position="0 0 1000" color="white" intensity="1.5"></lume-point-light>
<!-- Make the grid fill 75% width and 75% height of the scene, and center it. -->
<grid-layout size-mode="proportional proportional" size="0.75 0.75" align="0.5 0.5" mount-point="0.5 0.5">
<!-- Each direct child of the <grid-layout> element will be positioned (its position and size
properties will be set by the parent grid-layout). -->
<lume-node>
<!--
A way to create padding is make a smaller node inside its parent.
The <lume-dom-plane> element is a node that intends to contains traditional HTML
content (f.e. a <img> tag), but it also renders a WebGL plane for GPU-powered shading.
We give the WebGL plane a black coloring. We apply a material opacity in the JavaScript below.
-->
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/car-light-streaks.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/oakland-lake-merrit-sunset.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/goof-troops.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/hack-sesh.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/oreo-cookie.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/wooooo-graduation-sac-state-2014.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/ada-lovelace.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/albany-lizard.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/half-dome-yosemite.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/hawaiian-spider.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/hawaiian-starnight.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/joe-ana-anniversary-2020.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/jeremiahs-techie-selfie.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/mom-and-son.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/shelby-gt350.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/silhouettes.jpg" />
</lume-dom-plane>
</lume-node>
<lume-node>
<lume-dom-plane color="black" size-mode="proportional proportional" size="0.95 0.95">
<img src="https://assets.codepen.io/191583/thor-beach-sunset.jpg" />
</lume-dom-plane>
</lume-node>
</grid-layout>
</lume-scene>
html,
body {
background: #222;
width: 100%;
height: 100%;
margin: 0;
}
lume-scene {
/* Prevent touch scrolling from interfering with out pointermove handler. */
touch-action: none;
}
lume-node {
}
img {
display: block;
width: 100%;
height: 100%;
object-fit: contain;
background: black;
}
{
// Define LUME's HTML elements with their default names.
LUME.useDefaultNames();
/**
* @mixin
* @class ChildWatcher -
* A mixin class that gives your custom element a childrenChangedCallback()
* that runs any time your element's children have changed while your element
* is connected into a live DOM tree.
*
* For example:
*
* ```js
* @element('my-element')
* class MyElement extends ChildWatcher(HTMLElement) {
* childrenChangedCallback() {
* console.log('The new children are:', this.children)
* }
* }
* ```
* ```html
* <my-element>
* <div>...</div>
* <img />
* </my-element>
* ```
*
* This will log the two children of `<my-element>` when `<my-element>` is placed into the page's HTML tree.
*/
function ChildWatcher(Base) {
return class ChildWatcher extends Base {
connectedCallback() {
super.connectedCallback?.();
// When children have changed, recompute the layout.
this.observer = new MutationObserver(() =>
this.childrenChangedCallback?.()
);
this.observer.observe(this, { childList: true });
}
disconnectedCallback() {
super.disconnectedCallback?.();
this.observer.disconnect();
}
};
}
const { reactive, element, numberAttribute } = LUME;
@element("grid-layout")
class GridLayout extends ChildWatcher(LUME.Node) {
// If rows or columns is not provided, then the default is a square grid
// that fits all images (f.e. give 7 images, the grid rows and columns
// would be 3 and 3, for a total of 9 cells, and two cells would be empty
// in the last row).
@reactive @numberAttribute(0) rows;
@reactive @numberAttribute(0) columns;
connectedCallback() {
super.connectedCallback();
// Run an initial layout on connect, and also recompute layout whenever this.rows or this.columns change.
this.stop = LUME.autorun(() => {
this.layout(this.rows, this.columns);
});
}
disconnectedCallback() {
// Don't forget cleanup!
this.stop();
}
childrenChangedCallback() {
// Recompute layout any time this element's children have changed
// (it is batched, so happens once per macrotask, for better performance)
this.layout(this.rows, this.columns);
}
layout(rows, columns) {
// Calculate the grid rows and columns to be a square if rows/columns isn't provided.
if (!rows || !columns) {
const size = Math.ceil(Math.sqrt(this.children.length));
rows = size;
columns = size;
}
const cells = rows * columns;
const gridSize = this.calculatedSize; // [x, y, z] in px units
const cellWidth = gridSize.x / columns;
const cellHeight = gridSize.y / rows;
for (let i = 0; i < cells; i += 1) {
const node = this.children[i];
// If we have less nodes than total cells, quit early.
if (!node) break;
node.size = [cellWidth, cellHeight];
node.position = {
x: (i % columns) * cellWidth,
y: Math.floor(i / columns) * cellHeight
};
}
}
}
// The following is temporary way to set material opacity because it
// isn't exposed through the HTML interface yet, but this shows how
// we can manipulate the underlying Three.js objects if we so wish.
Array.from(document.querySelectorAll("lume-dom-plane")).forEach((node) => {
// Once the node's GL API is ready, then we can access underlying Three.js stuff.
node.on(LUME.Events.GL_LOAD, async () => {
// node.three in this case is a Three.js Mesh instance.
// Three.js Mesh docs: https://threejs.org/docs/index.html#api/en/objects/Mesh
node.three.material.opacity = 0.3;
// If we modify properties on the underlying Three.js objects, we need to
// manually signal to LUME that a re-render should happen:
node.needsUpdate();
});
});
Array.from(document.querySelectorAll("lume-point-light")).forEach((node) => {
// Once the node's GL API is ready, then we can access underlying Three.js stuff.
node.on(LUME.Events.GL_LOAD, async () => {
// node.three in this case is a Three.js PointLight instance.
// Three.js PointLight docs: https://threejs.org/docs/index.html#api/en/lights/PointLight
// A little bias adjustment helps make things look smooth when the light source is further away.
node.three.shadow.bias = -0.001;
// If we modify properties on the underlying Three.js objects, we need to
// manually signal to LUME that a re-render should happen:
node.needsUpdate();
});
});
const scene = document.querySelector("lume-scene");
const light = document.querySelector("lume-point-light");
const grid = document.querySelector("grid-layout");
const rotationAmount = 15;
// Add some interaction so we can see the shine from the light!
scene.addEventListener("pointermove", (event) => {
// Move the light on mouse move or finger drag.
light.position.x = event.clientX;
light.position.y = event.clientY;
// Rotate the grid a little bit too.
grid.rotation.y = -(
(event.clientX / scene.calculatedSize.x) * (rotationAmount * 2) -
rotationAmount
);
grid.rotation.x =
(event.clientY / scene.calculatedSize.y) * (rotationAmount * 3) -
rotationAmount;
});
}
View Compiled
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.