个人项目平台构建全记录:从创意到代码的实现之旅

本文详细记录了设计师Mike van der Sanden如何将个人作品集升级为互动式项目平台的技术实现过程,涵盖Nuxt.js框架选择、p5.js与Three.js性能对比、砌体网格布局开发等核心内容,为创意开发者提供实用参考。

两条路径的汇合

今年夏天,我创建了个人项目平台。这个决定并非完全有意为之,当我意识到自己的创作过程走向时,已经在这条路上走了一段距离。

作为设计师,并非每天都能充满灵感。特别是在设计与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多个项目追溯性地做这件事,所以未来我可能会分享更多过程和幕后内容。

这个平台尚未完成——这正是重点。这是一个与我的编码工具互动的空间,是分享草图以供进一步探索的空间,也是让过程本身保持可见的空间。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计