两条路径的汇合
今年夏天,我创建了个人项目平台。这个决定并非完全有意为之,当我意识到自己的创作过程走向时,已经在这条路上走了一段距离。
作为设计师,并非每天都能充满灵感。特别是在设计与AI领域快速变化的当下,有时很难看清大局。为此,我开始构建一个情绪板作为我的“快乐空间”,收集让我微笑的参考内容、梦想办公室的片段、引起共鸣的语录,以及随机的图像碎片。
技术选型与架构
既然我们在Codrops上,让我们谈谈代码。我有PHP和JavaScript背景,但想通过这个项目学习新技术。经过研究,我选择了Nuxt.js,因为它比Next.js更易设置。对于内容管理,我选择了几年前接触过的Prismic CMS——轻量级且功能足够。
核心交互组件实现
英雄区域设计
我希望能有让访客立即感受到作品特色的交互组件。在英雄区域,我设计了通过鼠标在画布上绘制对象的交互。这些对象与自然有联系——像承担许多个人项目一样,能够生长和繁荣。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public getPortion(): p5.Image {
const original = this.getNext();
if (!original) return null;
const ow = original.width;
const oh = original.height;
const sx = Math.random() * ow;
const sy = Math.random() * oh;
const loW = ow - sx;
const loH = oh - sy;
let sw = Math.round(loW * Math.random()) + 10;
let sh = Math.round(loH * Math.random()) + 10;
const copy = this.p.createImage(dw, dh);
copy.copy(original, sx, sy, sw, sh, dx, dy, dw, dh);
return copy;
}
|
页脚交互
为了平衡英雄区域,我也让页脚具有交互性。我使用了一个较早的草图作为基础,添加深度和纹理,使其感觉像抽象的海洋。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
public update() {
let duration: number = 128;
let progress: number = this.p.frameCount % duration;
if(progress == 0) this.iteration++;
let numRowsDrawn: number = this.numRows + 1 + this.iteration;
let colW: number = this.p.width / this.numCols;
let rowH: number = this.p.height / this.numRows;
let count = 0;
for (let y: number = this.iteration; y<numRowsDrawn; y++) {
let targetY: number = this.p.height - (y+1) * rowH + this.iteration * rowH;
let posY: number = this.p.map(progress, 0, duration, targetY, targetY+rowH);
const smoothing = 0.06;
this.currentMouseX += (this.p.mouseX - this.currentMouseX) * smoothing;
const mouseInfluence: number = this.p.map(this.currentMouseX, 0, this.p.width, .8, -.3);
let yInfluence: number = this.p.map(posY / this.numRows, 0, rowH, 1, this.numRows+1) * mouseInfluence;
let extraCols: number = Math.exp(yInfluence * Math.LN2);
let currentW: number = colW + extraCols * colW;
for (let x:number = 0; x<this.numCols; x++) {
let posX: number = x * currentW - (extraCols * yInfluence + 1) * colW;
if(posX > this.p.width) continue;
if(posX + currentW < 0) continue;
this.display(x, y, posX, posY, currentW, rowH);
count++;
}
}
}
|
砌体网格布局
我一直喜欢有很多内容的灵感网站,各种图像和视频在单独看时很强,但在不同背景下获得新意义。这就是我为案例概览想要的效果。
我决定使用砌体网格,并且不想使用插件,所以构建了这个CSS/JavaScript小工具,使用CSS Grid行来分布图像,JavaScript计算它应该跨越多少行,取决于CMS中设置的宽高比。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
function applyMasonry() {
const grid = document.querySelector('.masonry-grid');
const items = grid?.querySelectorAll('.masonry-item');
if (!grid || !items) return
const rowHeight = parseInt(getComputedStyle(grid).getPropertyValue('grid-auto-rows'))
const gap = parseInt(getComputedStyle(grid).getPropertyValue('gap') || 0)
items.forEach(item => {
const media = item.querySelector('.masonry-item__image-container')
const info = item.querySelector('.masonry-item__info-container')
if (!media || !info) return
const mediaHeight = media.getBoundingClientRect().height
const infoHeight = info.getBoundingClientRect().height
const itemHeight = mediaHeight + infoHeight
const rowSpan = Math.ceil((itemHeight + gap) / (rowHeight + gap))
item.style.gridRowEnd = `span ${rowSpan}`;
item.style.opacity = 1;
})
}
|
p5.js与Three.js的性能对比
为了在Instagram上发布我的标志,我创建了一个Processing草图,将标志放置在像素化的3D场景中旋转。现在我需要构建一个Web版本。
我的英雄和页脚组件都是p5.js,所以这是我的首选。但它很慢——真的很慢。无论我如何尝试优化,3D工作负载都严重影响了性能。几年前我只用过一次Three.js,但记得它处理3D相当好。虽然使用多个库可能不是最佳性能选择,但既然都是为了乐趣,我决定试一试。
使用Three.js版本,我可以为结构添加更多细节,与p5.js版本相比,性能仍然完美无缺。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
let instanceId: number = 0;
for (let z: number = 0; z < detail; z++) {
for (let y: number = 0; y < detail; y++) {
const flippedY: number = detail - 1 - y;
for (let x: number = 0; x < detail; x++) {
const sampleX: number = Math.floor((x / detail) * imgDetail);
const sampleY: number = Math.floor((flippedY / detail) * imgDetail);
const sampleZ: number = Math.floor((z / detail) * imgDetail);
const brightness1: number = getBrightnessAt(imgData, imgDetail, sampleX, sampleY);
const brightness2: number = getBrightnessAt(imgData, imgDetail, sampleZ, sampleY);
if (brightness1 < 100 && brightness2 < 100 && instanceId < maxInstances) {
dummy.position.set(
x * cellSize - (detail * cellSize) / 2,
y * cellSize - (detail * cellSize) / 2,
z * cellSize - (detail * cellSize) / 2
);
dummy.updateMatrix();
mesh.setMatrixAt(instanceId, dummy.matrix);
instanceId++;
}
}
}
}
|
资源与代码共享
我真心想鼓励人们开始自己的个人项目之旅,因此想分享资源和代码示例来帮助他们入门。当然,随着这个平台的推出,我必须为20多个项目追溯性地做这件事,所以未来我可能会分享更多过程和幕后内容。
这个平台尚未完成——这正是重点。这是一个与我的编码工具互动的空间,是分享草图以供进一步探索的空间,也是让过程本身保持可见的空间。