p5.js

213 阅读11分钟

起始

从谷歌浏览器的扩展程序中,找到一个新的扩展工具,叫做几枝,组成部分,一个是诗词展示,今日诗词的API,以及底下的波纹,字体库,还有颜色的一个库,整体很炫酷。刚一开始,以为底下的波纹使用canvas写的,但看了它的代码库,发现了一个新的js库,p5.js

前提

需要有 JavaScript 基础

文档

APIp5js.org/zh-Hans/ref…

示例p5js.org/zh-Hans/exa…

在线编辑器editor.p5js.org/

参考文档德育处主任juejin.cn/post/717345…

简介

p5.js 是个 JavaScript 创意编程程式库,其专注在让编程更易于使用及更加广泛的包容艺术家、设计师、教育家、初学者以及任何人!p5.js 是个免费及开源的软件因为我们相信所有人都应该能自由使用软件及用于学习软件的工具。

p5.js 使用绘图的比喻并有一副完整的绘画功能。除此之外,您也不单限于您的绘图画布。您可以将您整个浏览器页面当作您的绘图,这包括了 HTML5 物件,如文字、输入框、视频、摄像头及音频。

简单来说,p5.js 能更容易做出具有艺术感的作品(很能整活).

p5.jsProcessing 往浏览器延伸的一个 canvas库Processing 是使用 Java 编写的,而 Java 对于从事艺术工作的工友来说上手是有点难度的。于是,p5.js 应运而生!

使用方式

CDN

<script src="https://cdn.jsdelivr.net/npm/p5@[p5_version]/lib/p5.js"></script>
// `[p5_version] `改成指定版本号即可
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>

其他版本cdn.jsdelivr.net/npm/p5@1.5.…

npm

npm i p5 --save

import p5 from 'p5'
<div id="palette"></div>
let count = 0

const s = (sketch) => {
  sketch.setup = function() {
    sketch.createCanvas(400, 400) // 创建画布,传入画布尺寸
    sketch.background(120) // 设置画布背景色
  }

  sketch.draw = function() {
    let x = Math.sin(count) * 100 + 200
    let y = Math.cos(count) * 100 + 200
    sketch.circle(x, y, 50) // 创建圆形
    count += 0.1
  }
}

let palette = new p5(s, 'palette')


初步使用

我们先看一个简单的demo,创建一个圆。

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(200, 200) // 创建一个 200 * 200 像素的画布
    background(180, 180, 180) // 画布背景色 background(r, g, b)
  }

  function draw() {
    circle(60, 60, 100) // 画一个圆形
  }
</script>

  • setup(): 可以理解为 p5.js 的一个生命周期,创建画布的方法通常写在 setup() 里。
  • draw(): 同样可以理解为 p5.js 的一个生命周期,在这函数里的代码会以 60帧每秒 的速度执行。
  • createCanvas(): 创建画布的方法,这个方法是 p5.js 在全局创建的,传入画布的宽高后,p5.js 会自动在页面的最后插入一个 canvas 元素
  • background(): 设置背景色,可以分别传入 r、g、b ,该方法也是 p5.js 在全局创建的。
  • circle(): 创建圆形的方法,3个参数分别代表:圆心x坐标圆心y坐标直径

使用 p5.js ,你可以理解为用这个工具创造一个“有生命”的世界。

  • 创造世界的工作是放在 setup() 函数里的。
  • 活动的过程是放在 draw() 函数里。

启动函数 setup

setup() 是 p5.js 里一个很重要的方法,你可以简单的理解为 setup 是 p5.js 里的一个生命周期函数。在该函数里可以做很多初始化工作,比如创建画布并设置大小、画布背景色等。

setup() 在每个页面都只能出现一次,并且它不能在一开始执行后再次被调用。

let a = 0;

function setup() {
  background(0);
}

function draw() {
    noStroke();
    fill(102);
    rect(a++ % width, 10, 2, 80);
}

绘图 draw

draw() 是 p5.js 里另一个很重要的函数

draw() 会在 setup() 之后执行,并且会重复的执行。如果想打断 draw() 可以试用 noLoop() 方法。

draw() 每秒执行次数受到 frameRate() 影响,frameRate() 默认每秒60帧。如果需要修改帧率,可以直接传入指定数值,比如 frameRate(20)

// 初始化为0。这个变量表示线的垂直位置
let yPos = 0;
function setup() {
  // setup() runs once
  frameRate(30);
}
function draw() {
  // draw() loops forever, until stopped
  background(204);
  // 使得线的垂直位置向上移动。这样,每一帧都会使线的位置上升1个像素。
  yPos = yPos - 1;
  if (yPos < 0) {
    yPos = height;
  }
  // 起点和终点的y坐标都是 yPos
  // x坐标从画布的左边(0)延伸到右边(width)
  line(0, yPos, width, yPos);
}

p5绘制2D基础图形

p5.js 可以绘制 2D 和 3D 图形

point

点是 p5.js 的基础元素之一

point(x, y, [z])

point()接收3个参数,其中 x y 是必传参;如果是在 2D 画布里,z 不需要传

  • x 表示点在 x 轴的坐标
  • y 表示点在 y轴的坐标
<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(200, 200)
    background(220, 220, 220)
  }

  function draw() {
    point(100, 100)
  }
</script>

"图片自定义高度" height="" width=""

如果需要画一个更大的点,可以通过 strokeWeight() 方法设置

strokeWeight(10)

线段 line

// 注意上面的参数顺序,一定不能写错的。
line(x1, y1, [z1], x2, y2, [z2])

其中 z1z2 在 2D 情况下是不需要传的,所以语法变成这样:

line(x1, y1, x2, y2)
  • x1y1 代表起点坐标;
  • x2 y2 代表终点坐标;

使用 line() 方法会自动将起点和终点连接起来,形成一根线段。 "图片自定义高度" height="" width=""

<script>
  function draw() {
    line(60, 30, 130, 140)
  }
</script>

三角形 triangle

triangle(x1, y1, x2, y2, x3, y3)

三角形有3个点,每个点需要用2个坐标(x和y)来描述,所以triangle()一共要传入6个参数。

"图片自定义高度" height="" width=""

<script>
  function draw() {
    triangle(100, 30, 40, 140, 160, 140)
  }
</script>

正方形 square

square(x, y, s, [tl], [tr], [br], [bl])
  • xy 是正方形左上角的坐标
  • s 是正方形的边长

tl、tr、br、bl 是用来设置正方形的圆角半径,分别是 上左、上右、下右、下左。如果不传这几个参数,正方形的角默认是90°(直角)。 "图片自定义高度" height="" width=""

<script>
  function draw() {
    square(10, 10, 80)
  }
</script>

"图片自定义高度" height="" width="" 如果只传入1个半径值,那么后面3个圆角半径的值会取左上的圆角半径。

square(10, 10, 80, 10)

"图片自定义高度" height="" width="" 如果是传入2个圆角半径,那第三和第四个圆角半径的值会取第二个的值。

square(10, 10, 80, 10, 30)

矩形 rect

rect(x, y, w, [h], [tl], [tr], [br], [bl])
  • x y 是矩形左上角坐标位置
  • w 是边长

  • 如果只传3个参数,绘制出来的是正方形(长和宽的值都使用第三个参数)
  • 如果传4个参数的话,就可以分别设置长和宽了,第3和第4个参数分别是 w 和 y。
<script>
  function draw() {
    rect(10, 10, 60, 100) // 矩形
  }
</script>

四边形 quad

四边形有4个顶点,1个定点用2个参数表示 x 和 y 坐标。

quad(x1, y1, x2, y2, x3, y3, x4, y4)

"图片自定义高度" height="" width=""

<script>
  function draw() {
    quad(80, 40, 180, 40, 140, 80, 40, 80) // 四边形
  }
</script>

圆形 circle

circle(x, y, d)
  • x 和 y 是圆形的坐标
  • d 是圆的直径

"图片自定义高度" height="" width=""

<script>
  function draw() {
    circle(80, 80, 100) // 圆形
  }
</script>

椭圆 ellipse

ellipse(x, y, w, [h])

  • x y 确定了椭圆的圆心
  • w 椭圆在x轴的宽度
  • h 椭圆在y轴的高度

  • 如果只传3个参数,h 会取 w 的值,所以画出来的是正圆形。
  • 如果传4个参数,并且 w 和 h 的值不一样,那画出来就是一个椭圆。

"图片自定义高度" height="" width=""

<script>
  function draw() {
    ellipse(80, 80, 100, 50) // 椭圆
  }
</script>

vertex

所有形状都是由连接一系列顶点形成的。 vertex() 可用于定义点、线、三角形、四角形及多边形的顶点坐标。 它只能beginShape()endShape() 函数之间使用。

beginShape() 将开始记录形状的顶点而 endShape() 则停止记录

vertex(x, y)

"图片自定义高度" height="" width=""

strokeWeight(3);
beginShape(POINTS);
vertex(30, 20);
vertex(85, 20);
vertex(85, 75);
vertex(30, 75);
endShape();

WebGL模式下的坐标系与传统的2D绘图坐标系有所不同。 在WebGL中,坐标系的原点(0, 0)位于画布的中心,x轴的正方向指向右侧,y轴的正方向指向上方。

"图片自定义高度" height="" width=""

createCanvas(100, 100, WEBGL);
background(240, 240, 240);
fill(237, 34, 93);
noStroke();
beginShape();
vertex(0, 35);
vertex(35, 0);
vertex(0, -35);
vertex(-35, 0);
endShape();

背景色 background()

通常 background() 会在2个地方用到。

  • 一个是写在 setup() 里,在初始化画布时可以设置画布背景色。
  • 还可以写在 draw() 里,每次刷新画布都可以设置画布背景色。

写在 draw() 里,画布每次刷新都会重新设置一次背景色。

填充颜色 fill()

创建图像后,图像默认的填充色是白色。 要修改图像填充色,使用fill()方法即可。 "图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }
  function draw() {
    fill('#f2c0ca') // 设置填充色
    rect(30, 30, 100, 80)
    circle(220, 70, 80)
  }
</script>

需要注意的是,一旦设置了 fill() ,在它后面创建的图形都会使用相同的填充色.

如果希望后面的图形使用别的颜色,可以再重新调用一遍fill()进行设置。 "图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }
  function draw() {
    fill('#f2c0ca') // 设置填充色
    rect(30, 30, 100, 80)
    fill('yellow') // 重新设置填充色
    circle(220, 70, 80)
  }
</script>

无填充 noFill()

如果不想设置填充色,可以使用 noFill() 方法。 不填充的情况下,图形内部将会设置成透明,会直接显示在它下层的颜色,如果它下层没有其他元素,则会直接显示背景色。 "图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }

  function draw() {
    noFill()
    rect(30, 30, 100, 80)
    circle(220, 70, 80)
  }
</script>

边框颜色 stroke()

p5.js 创建出来的元素默认是有一个黑色边框,如果想要修改边框颜色,可以使用 stroke() 方法。 "图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }

  function draw() {
    stroke('red') // 设置边框颜色
    rect(30, 30, 100, 80)
    circle(220, 70, 80)
  }
</script>

fill() 一样,在使用 stroke() 设置完颜色之后的图形都会使用这个边框颜色。如果要再次修改边框颜色,只需再次使用 stroke() 即可。

无边框 noStroke()

如果不希望图形有边框,可以使用 noStroke() 方法。 "图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }

  function draw() {
    noStroke() // 无边框
    rect(30, 30, 100, 80)
    circle(220, 70, 80)
  }
</script>

设置边框粗细 strokeWeight()

使用strokeWeight()方法可以设置图形边框的粗细"图片自定义高度" height="" width=""

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(200)
  }

  function draw() {
    strokeWeight(10) // 设置边框粗细
    rect(30, 30, 100, 80)
    circle(220, 70, 80)
  }
</script>

动画

动画其实就是在修改元素属性的同时不断刷新画布

p5.js 里做动画效果其实很简单,只需要在 draw() 修改元素属性即可。

demo: 圆形图案跟随鼠标指针移动

<script src="https://cdn.jsdelivr.net/npm/p5@1.5.0/lib/p5.js"></script>
<script>
  function setup() {
    createCanvas(300, 300)
    background(100)
  }

  function draw() {
    background(100)
    circle(mouseX, mouseY, 40, 40)
  }
</script>

写波纹

定义变量
let xspacing = 8; //  定义了水平位置之间的距离。
let waveHeight; // 用来保存波的高度。
let maxwaves = 10; // 相加的波的总数
let theta; // 是一个角度值,用于控制波形的滚动速度。
let dx = new Array(maxwaves); // 是一个数组,用于存储每个波的陡峭度。
setup
function setup() {
    createCanvas(windowWidth, windowHeight);
    theta = random(10) // 随机设置 theta 的初始值。
    frameRate(60);
    for (let i = 0; i < maxwaves; i++) {
        dx[i] = random(0.1, 0.2); //使用一个循环来为每个波设置陡峭度
    }
    waveHeight = new Array(floor(width / xspacing)); // 是一个与画布宽度相匹配的数组。
}
draw
function draw() {
    background("white"); //画布背景色
    calcWave(dx);
    renderWave(2);
}
计算波的高度
function calcWave(dx) {
    // theta 增量(尝试赋予 ‘角速度’ 不同的数值)
    theta += 0.01 //波形滚动速度

    // 所有高度设为 0
    for (let i = 0; i < waveHeight.length; i++) {
        waveHeight[i] = 0;
    }
    // 累积波的高度
    for (let j = 0; j < maxwaves; j++) {
        let x = theta;

        for (let i = 0; i < waveHeight.length; i++) {
            // 正弦余弦交替
            x += dx[j];

            if (j % 2 === 0) waveHeight[i] += sin(x) * 9;
            else waveHeight[i] += cos(x) * 6;
            x -= 0.05
        }

    }
}
绘制波形
function renderWave(h) {

    // noStroke(0) //线条颜色 无

    // fill(92, 179, 204, 100);  //填充色
    strokeWeight(3);
    beginShape(POINTS)
    // vertex(width, height); // 添加一个顶点,坐标为画布的右下角。
    // vertex(0, height);  // 添加一个顶点,坐标为画布的左下角。
    for (let x = 0; x < waveHeight.length; x++) {
        point(x * (xspacing), width / h + waveHeight[x])
    }
    endShape()
}
填充
function renderWave(h) {

    noStroke(0) //线条颜色 无

    fill(92, 179, 204, 100);  //填充色
    // strokeWeight(3);
    beginShape()
    vertex(width, height); // 添加一个顶点,坐标为画布的右下角。
    vertex(0, height);  // 添加一个顶点,坐标为画布的左下角。
    for (let x = 0; x < waveHeight.length; x++) {
        vertex(x * (xspacing), width / h + waveHeight[x])
    }
    endShape()
}