Pen Settings



CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource


Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource


Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.


Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.




                * { margin: 0; }


                const PARTICLES_COUNT = 250

const vertexShaderSource = `
    // precision of our floats in GLSL
    // See for more info
   precision highp float;

   // The vertex indice as a float.
   // We can calculate the final vertex position using the indice
   attribute float a_indice;

   // Updated uniform time will be passed every frame so we can run our animation
   uniform float time;
   // Radius scale of movement for our particles.
   // We will update it every frame with new value animated in javascript using simple easing
   uniform float radiusScale;

   // Pass the particle count in shader construction step so we don't have to pass an extra uniform

   // GLSL does not have PI primitive, so we need to supply it ourselves
   const float PI = ${Math.PI};

   void main () {
      // Construct vec2 (x, y) position sing our indice that we supplied as a Float32Array

      // Calculate the position using our indice and time as inputs to simple sin / cos formulas.
      // Change the variable values to see the effect
      float step = PI * radiusScale / PARTICLES_COUNT;
      float timeScale = 0.001;
      float minRadius = 0.1;

      float x = sin(a_indice * radiusScale * 0.01 * step - time * timeScale) * (a_indice / PARTICLES_COUNT + minRadius);
      float y = cos(a_indice * step - time * timeScale) * (a_indice / PARTICLES_COUNT + minRadius);
      vec2 position = vec2(x, y);

      // WebGL expects us to supply the final position in 4 dimension - xyzw
      gl_Position = vec4(position, 0.0, 1.0);

      // Set our particle size depending on how far is it from the center
      float distFromCenter = distance(position, vec2(0.0));
      gl_PointSize = distFromCenter * 7.0 + 1.0;
  const fragmentShaderSource = `
     // Our fragment shader is super simple, just color each vertex uniformly with the same color
     precision highp float;
     void main () {
      gl_FragColor = vec4(vec3(0.6), 1.0);

// Create a canvas and append it to the DOM
const canvas = document.createElement('canvas')

// Obtain WebGLRenderingContext for drawing. 'experimental-webgl' is needed for IE11
const gl = canvas.getContext('webgl') || canvas.getContext('experimental-webgl')

// We need both radius scale and radius scale target so we can implement smooth easing to the new scale when it updates
let radiusScale = 0.5
let targetRadiusScale = radiusScale

// Need old time to calculate frame delta time for our animation
let oldTime = 0

let drawProgram
let timeLocation
let radiusScaleLocation

// Initialize our program
document.addEventListener('DOMContentLoaded', init)

function init () {
  // Handle viewport resize
  window.addEventListener('resize', resizeCanvas)
  // Create and link our program
  drawProgram = makeProgram(gl, {
  // Create array to hold our the indexes of our particles
  const indices = new Float32Array(PARTICLES_COUNT)
  const step = Math.PI * 8 / PARTICLES_COUNT
  for (let i = 0; i < PARTICLES_COUNT; i++) {
    indices[i] = i
  // Create a WebGLBuffer to hold our positions array
  const indicesBuffer = gl.createBuffer()
  // Bind our positionBuffer to the active gl.ARRAY_BUFFER
  gl.bindBuffer(gl.ARRAY_BUFFER, indicesBuffer)
  // Supply data to our positionBuffer using the current gl.ARRAY_BUFFER hook
  gl.bufferData(gl.ARRAY_BUFFER, indices, gl.STATIC_DRAW)
  // Query the a_indice input variable in our vertex shader from the linked program running on our GPU
  const positionLocation = gl.getAttribLocation(drawProgram, 'a_indice')
  // Enable the variable on our GPU
  // Point the position location to the active buffer and specify its layout
  // Second argument is "1" because we have one indice per vertex
  gl.vertexAttribPointer(positionLocation, 1, gl.FLOAT, false, 0, 0)
  // We need to explicitly allow our program before using it
  // Look up our uniform locations on the GPU
  timeLocation = gl.getUniformLocation(drawProgram, 'time')
  radiusScaleLocation = gl.getUniformLocation(drawProgram, 'radiusScale')
  // Start the render loop
  // Change radiusScale every 2000ms
  setInterval(() => {
    // We update targetRadiusScale so we can ease to it.
    const newScale = Math.random() * 20
    targetRadiusScale = newScale
    // Enable the next line to see the difference if we update radiusScale directly
    // radiusScale = newScale
  }, 2000)

function renderFrame (ts) {
  // Calculate time passed since the last frame was rendered for our easing formula
  let dt = (ts - oldTime) / 1000
  oldTime = ts
  // Cap our delta time to 1 / 30
  const maxDeltaTime = 1 / 30
  if (dt > maxDeltaTime) {
    dt = maxDeltaTime
  // Paint over the canvas with a rgba color
  gl.clearColor(0.8, 0.8, 0.8, 1.0)
  // Supply new elapsed time uniform to our shader
  gl.uniform1f(timeLocation, ts)
  // Ease the radiusScale to its correct value for smooth animation
  radiusScale += (targetRadiusScale - radiusScale) * dt
  // Supply the updated radiusScale to our shader
  gl.uniform1f(radiusScaleLocation, radiusScale)
  // Issue a render call with gl.LINES
  gl.drawArrays(gl.LINES, 0, PARTICLES_COUNT)
  // Issue a render call with gl.POINTS
  gl.drawArrays(gl.POINTS, 0, PARTICLES_COUNT)

  // Issue next render

function resizeCanvas() {
  // Multiply width and height to account for the device pixel ratio and prevent aliasing
  canvas.width = devicePixelRatio * innerWidth
  canvas.height = devicePixelRatio * innerHeight
  // Scale down to the actual layout width and height = `${innerWidth}px` = `${innerHeight}px`
  // Set the WebGL context viewPort
  // gl.drawingBufferWidth and gl.drawingBufferHeight represent the actual width / height of the current drawing buffer
  gl.viewport(0, 0, gl.drawingBufferWidth, gl.drawingBufferHeight)  

// -------- WebGL Utils --------

function makeShader (gl, { shaderType, shaderSource }) {
  // Create WebGLShader object with correct type
  const shader = gl.createShader(shaderType)
  // Pass our JS string containing the shader code for this shader
  gl.shaderSource(shader, shaderSource)
  // Compile our shader
  // Return if compilation was successful and there are no errors
  const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS)
  if (success) {
    return shader
  // Log the shader info for debugging purposes
  // Delete invalid WebGLShader object

function makeProgram (gl, { vertexShaderSource, fragmentShaderSource }) {
  // Create WebGLShader object with type gl.VERTEX_SHADER
  const vertexShader = makeShader(gl, {
    shaderType: gl.VERTEX_SHADER,
    shaderSource: vertexShaderSource,
  // Create WebGLShader object with type gl.FRAGMENT_SHADER
  const fragmentShader = makeShader(gl, {
    shaderType: gl.FRAGMENT_SHADER,
    shaderSource: fragmentShaderSource,

  // Create new WebGLProgram object
  const program = gl.createProgram()
  // Attach both shaders to the program. Ordering matters: vertex shader is always first
  // Attach vertex WebGLShader
  gl.attachShader(program, vertexShader)
  // Attach fragment WebGLShader
  gl.attachShader(program, fragmentShader)
  // Link the program on the device GPU
  // Check if the program was linked successfully by checking its gl.LINK_STATUS  
  const success = gl.getProgramParameter(program, gl.LINK_STATUS)
  if (success) {
    return program
  // Log the program info for debugging
  // Delete invalid program