Merge background into main
This commit is contained in:
parent
e7d4c1835e
commit
bc5074b28d
7 changed files with 158 additions and 188 deletions
|
|
@ -1,4 +1,3 @@
|
||||||
import { Background } from '../page/background/background';
|
|
||||||
import { Contact } from '../page/contact/contact.html';
|
import { Contact } from '../page/contact/contact.html';
|
||||||
import { Header } from '../page/header/header';
|
import { Header } from '../page/header/header';
|
||||||
import { ImageViewer } from '../page/image-viewer/image-viewer';
|
import { ImageViewer } from '../page/image-viewer/image-viewer';
|
||||||
|
|
@ -26,7 +25,6 @@ import { towers } from './projects/towers';
|
||||||
import { CV, Email, GitHubLink, LinkedIn } from './shared';
|
import { CV, Email, GitHubLink, LinkedIn } from './shared';
|
||||||
|
|
||||||
const main = new Main(
|
const main = new Main(
|
||||||
new Background(1, 1),
|
|
||||||
new Header({
|
new Header({
|
||||||
name: 'András Schmelczer',
|
name: 'András Schmelczer',
|
||||||
image: me,
|
image: me,
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { html } from '../../types/html';
|
|
||||||
import './background.scss';
|
|
||||||
|
|
||||||
export const generate = (): html => `
|
|
||||||
<div id="background"></div>
|
|
||||||
`;
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
@use '../../style/mixins' as *;
|
|
||||||
|
|
||||||
#background {
|
|
||||||
transform-style: preserve-3d;
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
> div {
|
|
||||||
position: absolute;
|
|
||||||
width: 140px;
|
|
||||||
border-radius: 1000px;
|
|
||||||
transition: background-color var(--transition-time);
|
|
||||||
|
|
||||||
&:nth-child(odd) {
|
|
||||||
background-color: #fff9e0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:nth-child(even) {
|
|
||||||
background-color: #ffd6d6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include in-dark-mode {
|
|
||||||
#background > div {
|
|
||||||
background-color: #2c477a;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,146 +0,0 @@
|
||||||
import { getHeight } from '../../helper/get-height';
|
|
||||||
import { mix } from '../../helper/mix';
|
|
||||||
import { Random } from '../../helper/random';
|
|
||||||
import { sum } from '../../helper/sum';
|
|
||||||
import { PageElement } from '../page-element';
|
|
||||||
import { generate } from './background.html';
|
|
||||||
|
|
||||||
export class Background extends PageElement {
|
|
||||||
private static readonly perspective = 5;
|
|
||||||
private static readonly zMin = 6;
|
|
||||||
private static readonly zMax = 50;
|
|
||||||
private static readonly minHeight = 360;
|
|
||||||
private static readonly maxHeight = 740;
|
|
||||||
private static readonly minBlobCount = 30;
|
|
||||||
private static readonly blobCountScaler = 0.05;
|
|
||||||
private static readonly stableSeed = 51;
|
|
||||||
|
|
||||||
private random = new Random();
|
|
||||||
private stableRandom = new Random();
|
|
||||||
private blobs: Array<HTMLElement> = [];
|
|
||||||
private windowHeight = 0;
|
|
||||||
private contentHeight = 0;
|
|
||||||
|
|
||||||
public constructor(
|
|
||||||
private readonly topOffsetElementCount: number,
|
|
||||||
private readonly bottomOffsetElementCount: number
|
|
||||||
) {
|
|
||||||
super(generate());
|
|
||||||
}
|
|
||||||
|
|
||||||
protected initialize() {
|
|
||||||
super.initialize();
|
|
||||||
this.drawIfNecessary();
|
|
||||||
}
|
|
||||||
|
|
||||||
private maintainBlobCount() {
|
|
||||||
const targetCount = Math.max(
|
|
||||||
Background.minBlobCount,
|
|
||||||
Math.ceil(window.innerWidth * Background.blobCountScaler)
|
|
||||||
);
|
|
||||||
const deltaCount = targetCount - this.blobs.length;
|
|
||||||
|
|
||||||
for (let i = 0; i < deltaCount; i++) {
|
|
||||||
const blob = this.createBlob();
|
|
||||||
this.blobs.push(blob);
|
|
||||||
this.htmlRoot.appendChild(blob);
|
|
||||||
}
|
|
||||||
for (let i = 0; i < -deltaCount; i++) {
|
|
||||||
const blob = this.blobs.pop();
|
|
||||||
this.htmlRoot.removeChild(blob!);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private createBlob(): HTMLElement {
|
|
||||||
const blob = document.createElement('div');
|
|
||||||
const z = this.random.inInterval(Background.zMin, Background.zMax);
|
|
||||||
const endXSpan =
|
|
||||||
((1 / Background.perspective) * (Background.zMax + Background.perspective)) / 2;
|
|
||||||
|
|
||||||
const x = this.random.inInterval(
|
|
||||||
mix(0, -(endXSpan - 0.5), z / Background.zMax),
|
|
||||||
mix(1, 1 + endXSpan - 0.5, z / Background.zMax)
|
|
||||||
);
|
|
||||||
|
|
||||||
blob.style.left = `${x * 100}%`;
|
|
||||||
blob.style.zIndex = (-z).toFixed(0);
|
|
||||||
blob.style.opacity = (
|
|
||||||
1 -
|
|
||||||
(z - Background.zMin) / (Background.zMax - Background.zMin)
|
|
||||||
).toString();
|
|
||||||
blob.style.height = `${this.random.inInterval(
|
|
||||||
Background.minHeight,
|
|
||||||
Background.maxHeight
|
|
||||||
)}px`;
|
|
||||||
|
|
||||||
return blob;
|
|
||||||
}
|
|
||||||
|
|
||||||
private drawIfNecessary() {
|
|
||||||
const siblings = this.getSiblings();
|
|
||||||
const currentContentHeight = sum(siblings.map(getHeight));
|
|
||||||
|
|
||||||
if (
|
|
||||||
window.innerHeight !== this.windowHeight ||
|
|
||||||
currentContentHeight !== this.contentHeight
|
|
||||||
) {
|
|
||||||
this.windowHeight = window.innerHeight;
|
|
||||||
this.contentHeight = currentContentHeight;
|
|
||||||
this.maintainBlobCount();
|
|
||||||
|
|
||||||
this.randomizeBlobs(
|
|
||||||
sum(siblings.slice(0, this.topOffsetElementCount).map(getHeight)),
|
|
||||||
sum(siblings.slice(-this.bottomOffsetElementCount).map(getHeight))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
requestAnimationFrame(this.drawIfNecessary.bind(this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private getSiblings(): Array<HTMLElement> {
|
|
||||||
return Array.prototype.slice
|
|
||||||
.call(this.htmlRoot.parentElement!.childNodes)
|
|
||||||
.filter((n: HTMLElement) => n !== this.htmlRoot);
|
|
||||||
}
|
|
||||||
|
|
||||||
private randomizeBlobs(topOffset: number, bottomOffset: number) {
|
|
||||||
this.stableRandom.seed = Background.stableSeed;
|
|
||||||
this.blobs.forEach((b) => {
|
|
||||||
const z = -parseFloat(b.style.zIndex);
|
|
||||||
const y = this.getRandomYInSafeArea(
|
|
||||||
z,
|
|
||||||
topOffset,
|
|
||||||
bottomOffset,
|
|
||||||
parseFloat(b.style.height)
|
|
||||||
);
|
|
||||||
|
|
||||||
b.style.transform = `translate3D(0, ${y}px, ${-z}px) rotate(-20deg)`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private getRandomYInSafeArea(
|
|
||||||
z: number,
|
|
||||||
topOffset: number,
|
|
||||||
bottomOffset: number,
|
|
||||||
height: number
|
|
||||||
): number {
|
|
||||||
const farTop = -(
|
|
||||||
((this.windowHeight / 2 - topOffset) / Background.perspective) *
|
|
||||||
(Background.zMax + Background.perspective) -
|
|
||||||
this.windowHeight / 2
|
|
||||||
);
|
|
||||||
|
|
||||||
const farBottom = Math.min(
|
|
||||||
((this.windowHeight / 2 - bottomOffset) / Background.perspective) *
|
|
||||||
(Background.zMax + Background.perspective) -
|
|
||||||
this.windowHeight / 2 +
|
|
||||||
this.contentHeight,
|
|
||||||
this.contentHeight - height
|
|
||||||
);
|
|
||||||
|
|
||||||
return this.stableRandom.inInterval(
|
|
||||||
mix(topOffset, farTop, z / Background.zMax),
|
|
||||||
farBottom
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { html } from '../../types/html';
|
import { html } from '../../types/html';
|
||||||
import './main.scss';
|
import './main.scss';
|
||||||
|
|
||||||
export const generate = (): html => `
|
export const generate = (perspective: number): html => `
|
||||||
<main></main>
|
<main style="perspective: ${perspective}px"></main>
|
||||||
`;
|
`;
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ main {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
perspective: 5px;
|
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
|
||||||
// chrome scrolling does not work on PC without this
|
// chrome scrolling does not work on PC without this
|
||||||
|
|
@ -22,4 +21,30 @@ main {
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
> .blob {
|
||||||
|
top: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 140px;
|
||||||
|
border-radius: 1000px;
|
||||||
|
transition: background-color var(--transition-time);
|
||||||
|
|
||||||
|
&:nth-child(odd) {
|
||||||
|
background-color: #fff9e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:nth-child(even) {
|
||||||
|
background-color: #ffd6d6;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include in-dark-mode {
|
||||||
|
main > .blob {
|
||||||
|
background-color: #2c477a;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,142 @@
|
||||||
|
import { getHeight } from '../../helper/get-height';
|
||||||
|
import { mix } from '../../helper/mix';
|
||||||
|
import { Random } from '../../helper/random';
|
||||||
|
import { sum } from '../../helper/sum';
|
||||||
import { PageElement } from '../page-element';
|
import { PageElement } from '../page-element';
|
||||||
import { generate } from './main.html';
|
import { generate } from './main.html';
|
||||||
|
|
||||||
export class Main extends PageElement {
|
export class Main extends PageElement {
|
||||||
|
private static readonly perspective = 5;
|
||||||
|
private static readonly zMin = 6;
|
||||||
|
private static readonly zMax = 50;
|
||||||
|
private static readonly minHeight = 360;
|
||||||
|
private static readonly maxHeight = 740;
|
||||||
|
private static readonly minBlobCount = 30;
|
||||||
|
private static readonly blobCountScaler = 0.05;
|
||||||
|
private static readonly stableSeed = 51;
|
||||||
|
|
||||||
|
private readonly topOffsetElementCount = 1;
|
||||||
|
private readonly bottomOffsetElementCount = 1;
|
||||||
|
|
||||||
|
private random = new Random();
|
||||||
|
private stableRandom = new Random();
|
||||||
|
private blobs: Array<HTMLElement> = [];
|
||||||
|
private windowHeight = 0;
|
||||||
|
private contentHeight = 0;
|
||||||
|
|
||||||
constructor(...children: Array<PageElement | string>) {
|
constructor(...children: Array<PageElement | string>) {
|
||||||
const actualChildren = children.map((c) =>
|
const actualChildren = children.map((c) =>
|
||||||
c instanceof PageElement ? c : new PageElement(c)
|
c instanceof PageElement ? c : new PageElement(c)
|
||||||
);
|
);
|
||||||
|
|
||||||
super(generate(), actualChildren);
|
super(generate(Main.perspective), actualChildren);
|
||||||
actualChildren.forEach((c) => this.attachElement(c));
|
actualChildren.forEach((c) => this.attachElement(c));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected initialize() {
|
||||||
|
super.initialize();
|
||||||
|
this.drawIfNecessary();
|
||||||
|
}
|
||||||
|
|
||||||
|
private maintainBlobCount() {
|
||||||
|
const targetCount = Math.max(
|
||||||
|
Main.minBlobCount,
|
||||||
|
Math.ceil(window.innerWidth * Main.blobCountScaler)
|
||||||
|
);
|
||||||
|
const deltaCount = targetCount - this.blobs.length;
|
||||||
|
|
||||||
|
for (let i = 0; i < deltaCount; i++) {
|
||||||
|
const blob = this.createBlob();
|
||||||
|
this.blobs.push(blob);
|
||||||
|
this.htmlRoot.appendChild(blob);
|
||||||
|
}
|
||||||
|
for (let i = 0; i < -deltaCount; i++) {
|
||||||
|
const blob = this.blobs.pop();
|
||||||
|
this.htmlRoot.removeChild(blob!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private createBlob(): HTMLElement {
|
||||||
|
const blob = document.createElement('div');
|
||||||
|
blob.className = 'blob';
|
||||||
|
const z = this.random.inInterval(Main.zMin, Main.zMax);
|
||||||
|
const endXSpan = ((1 / Main.perspective) * (Main.zMax + Main.perspective)) / 2;
|
||||||
|
|
||||||
|
const x = this.random.inInterval(
|
||||||
|
mix(0, -(endXSpan - 0.5), z / Main.zMax),
|
||||||
|
mix(1, 1 + endXSpan - 0.5, z / Main.zMax)
|
||||||
|
);
|
||||||
|
|
||||||
|
blob.style.left = `${x * 100}%`;
|
||||||
|
blob.style.zIndex = (-z).toFixed(0);
|
||||||
|
blob.style.opacity = (1 - (z - Main.zMin) / (Main.zMax - Main.zMin)).toString();
|
||||||
|
blob.style.height = `${this.random.inInterval(Main.minHeight, Main.maxHeight)}px`;
|
||||||
|
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private drawIfNecessary() {
|
||||||
|
const siblings = this.getSiblings();
|
||||||
|
const currentContentHeight = sum(siblings.map(getHeight));
|
||||||
|
|
||||||
|
if (
|
||||||
|
window.innerHeight !== this.windowHeight ||
|
||||||
|
currentContentHeight !== this.contentHeight
|
||||||
|
) {
|
||||||
|
this.windowHeight = window.innerHeight;
|
||||||
|
this.contentHeight = currentContentHeight;
|
||||||
|
this.maintainBlobCount();
|
||||||
|
|
||||||
|
this.randomizeBlobs(
|
||||||
|
sum(siblings.slice(0, this.topOffsetElementCount).map(getHeight)),
|
||||||
|
sum(siblings.slice(-this.bottomOffsetElementCount).map(getHeight))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
requestAnimationFrame(this.drawIfNecessary.bind(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSiblings(): Array<HTMLElement> {
|
||||||
|
return Array.prototype.slice
|
||||||
|
.call(this.htmlRoot.childNodes)
|
||||||
|
.filter((n: HTMLElement) => !n.classList.contains('blob'));
|
||||||
|
}
|
||||||
|
|
||||||
|
private randomizeBlobs(topOffset: number, bottomOffset: number) {
|
||||||
|
this.stableRandom.seed = Main.stableSeed;
|
||||||
|
this.blobs.forEach((b) => {
|
||||||
|
const z = -parseFloat(b.style.zIndex);
|
||||||
|
const y = this.getRandomYInSafeArea(
|
||||||
|
z,
|
||||||
|
topOffset,
|
||||||
|
bottomOffset,
|
||||||
|
parseFloat(b.style.height)
|
||||||
|
);
|
||||||
|
|
||||||
|
b.style.transform = `translate3D(0, ${y}px, ${-z}px) rotate(-20deg)`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getRandomYInSafeArea(
|
||||||
|
z: number,
|
||||||
|
topOffset: number,
|
||||||
|
bottomOffset: number,
|
||||||
|
height: number
|
||||||
|
): number {
|
||||||
|
const farTop = -(
|
||||||
|
((this.windowHeight / 2 - topOffset) / Main.perspective) *
|
||||||
|
(Main.zMax + Main.perspective) -
|
||||||
|
this.windowHeight / 2
|
||||||
|
);
|
||||||
|
|
||||||
|
const farBottom = Math.min(
|
||||||
|
((this.windowHeight / 2 - bottomOffset) / Main.perspective) *
|
||||||
|
(Main.zMax + Main.perspective) -
|
||||||
|
this.windowHeight / 2 +
|
||||||
|
this.contentHeight,
|
||||||
|
this.contentHeight - height
|
||||||
|
);
|
||||||
|
|
||||||
|
return this.stableRandom.inInterval(mix(topOffset, farTop, z / Main.zMax), farBottom);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue