Vertex data can be interleaved, one record per vertex in one buffer as in the cover wall, or separate, one tightly packed buffer per attribute as in Vertex Attributes. The stride and offset of vertexAttribPointer() describe either:

Interleaving keeps each vertex's bytes together and suits static meshes. Separate buffers let you rewrite one attribute, such as animated positions, while the rest stay put. Keep strides and offsets multiples of 4.
<!doctype html>
<style>
body { margin: 0; font: 11px system-ui, sans-serif; background: #f7f4ee; color: #333; }
canvas { display: block; width: 100%; max-width: 600px; }
.layout { display: flex; max-width: 600px; gap: 10px; }
.layout > div { flex: 1; }
.bytes { display: flex; margin: 3px 0; }
.bytes span { flex: 1; text-align: center; padding: 2px 0; color: #fff; border-right: 1px solid #f7f4ee; font-size: 10px; }
.p { background: #1f5f8b; } .c { background: #e09a10; }
</style>
<canvas id="view" width="600" height="200"></canvas>
<div class="layout">
<div><b>Interleaved</b> (stride 20, offsets 0 and 8)
<div class="bytes"><span class="p">x</span><span class="p">y</span><span class="c">r</span><span class="c">g</span><span class="c">b</span><span class="p">x</span><span class="p">y</span><span class="c">r</span><span class="c">g</span><span class="c">b</span></div></div>
<div><b>Separate</b> (stride 0, offset 0 each; positions rewritten per frame)
<div class="bytes"><span class="p">x</span><span class="p">y</span><span class="p">x</span><span class="p">y</span><span class="p">x</span><span class="p">y</span></div>
<div class="bytes"><span class="c">r</span><span class="c">g</span><span class="c">b</span><span class="c">r</span><span class="c">g</span><span class="c">b</span></div></div>
</div>
<script>
const gl = document.getElementById('view').getContext('webgl2');
const program = gl.createProgram();
for (const [type, src] of [[gl.VERTEX_SHADER, `#version 300 es
layout(location = 0) in vec2 aPos; layout(location = 1) in vec3 aColor; out vec3 vColor;
void main() { vColor = aColor; gl_Position = vec4(aPos, 0.0, 1.0); }`], [gl.FRAGMENT_SHADER, `#version 300 es
precision mediump float; in vec3 vColor; out vec4 c; void main() { c = vec4(vColor, 1.0); }`]]) {
const s = gl.createShader(type);
gl.shaderSource(s, src); gl.compileShader(s);
gl.attachShader(program, s);
}
gl.linkProgram(program);
gl.useProgram(program);
const N = 24; // a ribbon: N pairs of vertices as a triangle strip
function ribbon(x0, t) {
const pos = [];
for (let i = 0; i < N; i++) {
const x = x0 + (i / (N - 1)) * 0.85, y = Math.sin(i * 0.5 + t) * 0.25;
pos.push(x, y - 0.3, x, y + 0.3);
}
return pos;
}
const colors = [];
for (let i = 0; i < N * 2; i++) { const k = i / (N * 2); colors.push(0.12 + 0.76 * k, 0.37 + 0.23 * k, 0.55 - 0.49 * k); }
// Interleaved: x, y, r, g, b per vertex in ONE buffer (static).
const interleaved = [];
ribbon(-0.95, 0).forEach((v, i, all) => {
if (i % 2 === 0) interleaved.push(all[i], all[i + 1], ...colors.slice((i / 2) * 3, (i / 2) * 3 + 3));
});
const vaoA = gl.createVertexArray();
gl.bindVertexArray(vaoA);
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(interleaved), gl.STATIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 20, 0); // 5 floats = 20 bytes per vertex
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 20, 8);
gl.enableVertexAttribArray(0); gl.enableVertexAttribArray(1);
// Separate: positions in one DYNAMIC buffer, colours in another STATIC one.
const vaoB = gl.createVertexArray();
gl.bindVertexArray(vaoB);
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, N * 2 * 2 * 4, gl.DYNAMIC_DRAW);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0); // stride 0: tightly packed
gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.vertexAttribPointer(1, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(0); gl.enableVertexAttribArray(1);
function frame(ms) {
gl.clearColor(0.97, 0.96, 0.93, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.bindVertexArray(vaoA);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, N * 2);
gl.bindVertexArray(vaoB);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer); // rewrite one attribute, colours stay put
gl.bufferSubData(gl.ARRAY_BUFFER, 0, new Float32Array(ribbon(0.1, ms * 0.003)));
gl.drawArrays(gl.TRIANGLE_STRIP, 0, N * 2);
requestAnimationFrame(frame);
}
requestAnimationFrame(frame);
</script>