Pen Settings

HTML

CSS

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. You can use the CSS from another Pen by using it's URL and the proper URL extention.

+ add another resource

JavaScript

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

Packages

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.

Behavior

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.

HTML

              
                <main>
  <section>
    <h1>Rough Edges Paint Worklet</h1>
    <p><strong>If you Fork this:</strong> The pen's JS Panel is loaded as a paint worklet. So if you fork it, your URL has changed and you need to update it. Otherwise it will keep referring to this pen's JS!</p>
    <p>All the entries have the "rough-edge" worklet applied. There are 6 custom properties to control the edge generation:</p>
  </section>
  <details>
    <summary>show the props</summary>
    <dl>
    <dt><code>--rough-edge-padding: &lt;length&gt;</code></dt>
    <dd>Defines the "area" in which the curves of the edge will generate. By default, the severity of the curves will also relate to this value.</dd>
    <dt><code>--rough-edge-severity: &lt;length&gt;</code></dt>
    <dd>Sets the maximum height for generating the curves. The higher the value, the higher the curves.</dd>
    <dt><code>--rough-edge-max-segments: &lt;integer&gt;</code></dt>
    <dd>Sets the maximum number of horizontal segments, for which curves will be generated. Per default, the number of generated segments is based on the length of the segment</dd>
    <dt><code>--rough-edge-segment-length: &lt;length&gt;</code></dt>
    <dd>Sets the length of a horizontal segment. The smaller the size, the more segments for a given length will be generated, which will create more curves for the edge.</dd>
    <dt><code>--rough-edge-background: &lt;color&gt;</code></dt>
    <dd>Sets the color of the entire shape. Does not support gradients (yet!).</dd>
    <dt><code>--rough-edge-debug: &lt;integer&gt;</code></dt>
    <dd>Enables the debugging mode. In this mode, many of the points will be visualized, such as the A & B Bezier Points, as well as the segments and the edge-padding. Nice to see what's happening!</dd>
  </dl>
  </details>
  <article class="rough-edge  post">
    <h2>Entry #1</h2>
    <p>Cupcake ipsum dolor sit amet. Cheesecake caramels donut cupcake cupcake sweet roll. Cake tart I love pudding I love lemon drops croissant cake. Tootsie roll topping danish halvah I love jelly beans halvah candy.</p>
  </article>
  <article class="rough-edge  post">
    <h2>Entry #2</h2>
    <p>Dessert muffin croissant liquorice marshmallow. Shortbread jelly beans apple pie sweet tiramisu chocolate bar marshmallow I love marshmallow. Macaroon chocolate marzipan sugar plum sugar plum icing. Candy canes cupcake lemon drops gummi bears powder caramels tiramisu sesame snaps.</p>
  </article>
  <article class="rough-edge  post">
    <h2>Entry #3</h2>
    <p>Muffin pie tiramisu sesame snaps jelly-o croissant caramels chocolate marzipan. Pastry I love cookie sweet shortbread toffee cookie. I love sweet sugar plum chocolate bar I love halvah cookie.</p>
  </article>
</main>
<script>
  if (CSS.paintWorklet) {
    window.CSS.registerProperty({
      name: '--rough-edge-padding',
      syntax: '<length>',
      inherits: false,
      initialValue: '16px'
    });
    window.CSS.registerProperty({
      name: '--rough-edge-background',
      syntax: '<color>',
      inherits: false,
      initialValue: '#fff'
    });
    window.CSS.registerProperty({
      name: '--rough-edge-debug',
      syntax: '<integer>',
      inherits: false,
      initialValue: '0'
    });
    window.CSS.registerProperty({
      name: '--rough-edge-severity',
      syntax: '<length>',
      inherits: false,
      initialValue: '0px'
    });
    window.CSS.registerProperty({
      name: '--rough-edge-segment-length',
      syntax: '<length>',
      inherits: false,
      initialValue: '0px'
    });
    window.CSS.registerProperty({
      name: '--rough-edge-max-segments',
      syntax: '<integer>',
      inherits: false,
      initialValue: '0'
    });
    CSS.paintWorklet.addModule('/thomassemmler/pen/vYZogLx.js');
  }
</script>
              
            
!

CSS

              
                :root {
  background-color: turquoise;
}

body {
  display: grid;
  grid-template-columns: 1fr [content-start] minmax(auto, 740px) [content-end] 1fr;
}

main {
  grid-column: content-start / content-end;
  display: grid;
  grid-template-columns: repeat(1, 1fr);
  gap: 2em;
  
  h1 {
   grid-column: 1 / -1;
   grid-row: 1;
   font-size: 2.5em;
  }
  
  h2 {
    font-size: 1.5em;
  }
  
  & > .post {
    padding-inline: var(--rough-edge-padding);
    padding-block-end: var(--rough-edge-padding);
    border-radius: 3px;
    
    &:nth-of-type(1) {
      --rough-edge-background: honeydew;
    }
    
    &:nth-of-type(2) {
      --rough-edge-padding: clamp(1em, 10vw, 10em);
      --rough-edge-segment-length: 50;
    }
    
    &:nth-of-type(3) {
      --rough-edge-debug: 1;
      --rough-edge-max-segments: 3;
      --rough-edge-severity: 3em;
    }
  }
}

dl {
  dt + dd + * {
    margin-block-start: 1em;
  }
}

// This where the paintlet is being 
.rough-edge {
  padding-block-start: var(--rough-edge-padding);
  background: paint(rough-edge);
  min-height: var(--rough-edge-padding);
}
              
            
!

JS

              
                /**
  Much credit needs to go to Leif Niemczik (https://leifs.website/), who took a large chunk of his time not only to explain the math of how I could make sure that curves had mirrored bezier points (and thus smooth), but he even coded it into a demo and then helped me extract it.
*/

if (typeof registerPaint !== "undefined") {
  registerPaint(
    "rough-edge",
    class {
      static get inputProperties() {
        return [
          '--rough-edge-padding', 
          '--rough-edge-debug', 
          '--rough-edge-background',
          '--rough-edge-severity',
          '--rough-edge-segment-length',
          '--rough-edge-max-segments'
        ]
      }
      
      drawCurve(_ctx, _curvePosition, _edgePadding, _startX = 0, _endX, _lastCurve, _curveSeverity = undefined, _debug = undefined) {
        let curveYPos = _curvePosition;
        let length = _endX - _startX;

        // random numbers
        let randomYMax = _curveSeverity || _edgePadding;
        // random 1 / -1
        let randomYDir = Math.ceil( (Math.random() - .5) * 2 ) < 1 ? -1 : 1;
        // random number 0 / 0.5
        let randomAX = Math.random() * (0.25 - 0.15) + 0.15;
        // random number 0.5 / 1
        let randomBX = Math.random() * (0.85 - 0.75) + 0.75;
        // random number 16 / 32
        let randomY = Math.floor(Math.random() * (randomYMax - (randomYMax / 2)) +1) + (randomYMax / 2);
        let randomBY = Math.floor(Math.random() * randomYMax) + 1;

        // Start & End
        let startAt = { 
          x: _startX, 
          y: _lastCurve.curveY ? _lastCurve.curveY:randomY
        };
        let endAt = { 
          x: _endX,
          y: randomY
        };

        // Bezier-Handle Points
        let bezierA = {
          x: _lastCurve.handleX ? (_startX + _lastCurve.handleX):(length * randomAX),
          y: _lastCurve.handleY ? (_lastCurve.curveY + _lastCurve.handleY):randomY
        };

        let bezierB = {
          x: _startX + (length * randomBX.toFixed(2)), 
          y: randomBY
        };
        
        let passOn = {
          handleX: endAt.x - bezierB.x,
          handleY: endAt.y - bezierB.y,
          curveY: endAt.y
        };

        if (_debug) {
          // draw a curve right in the middle
          _ctx.beginPath();
          _ctx.setLineDash([]);
          _ctx.strokeStyle = '#000000'
          _ctx.moveTo(startAt.x, startAt.y);
          // the curve!
          _ctx.bezierCurveTo(
            bezierA.x, bezierA.y, // bezier point a
            bezierB.x, bezierB.y, // bezier point b
            endAt.x, endAt.y // endpoint
          );
          _ctx.stroke();
          _ctx.closePath();

          // draw the line from start, to the bezier points and to the end
          _ctx.beginPath();
          _ctx.strokeStyle = '#9e42f5';
          _ctx.setLineDash([4, 4]);
          _ctx.moveTo(startAt.x, startAt.y);
          _ctx.lineTo(bezierA.x, bezierA.y);
          _ctx.lineTo(bezierB.x, bezierB.y);
          _ctx.lineTo(endAt.x, endAt.y);
          _ctx.stroke();
          _ctx.closePath();

          // draw the bezier points as circles
          _ctx.beginPath();
          _ctx.fillStyle = '#ff0000';
          _ctx.arc(bezierA.x, bezierA.y, 4, 0, 2 * Math.PI);
          _ctx.fill();
          _ctx.closePath();

          _ctx.beginPath();
          _ctx.fillStyle = '#0000ff';
          _ctx.arc(bezierB.x, bezierB.y, 4, 0, 2 * Math.PI);
          _ctx.fill();
          _ctx.closePath();

        } else {
          // the curve!
          _ctx.bezierCurveTo(
            bezierA.x, bezierA.y, // bezier point a
            bezierB.x, bezierB.y, // bezier point b
            endAt.x, endAt.y // endpoint
          );
        }

        return passOn;
      }
      
      paint(ctx, geom, props) {
        // get values from custom properties
        const edgePadding = props.get('--rough-edge-padding').value;
        const debug = props.get('--rough-edge-debug').value;
        const curveSeverity = props.get('--rough-edge-severity').value;
        const segmentCustomLength = props.get('--rough-edge-segment-length').value;
        const maxSegments = props.get('--rough-edge-max-segments').value;
        const background = props.get('--rough-edge-background').toString();

        // Segments - determines, how many individual beziercurves will be drawn, per default, grows with base-length,  unless a fixed number of segments is determined
        const segmentBaseLength = segmentCustomLength || 100;
        const segments = maxSegments || Math.floor(geom.width / segmentBaseLength);
        const segmentLength = geom.width / segments;
       
        const curvePosition = edgePadding;
        const firstCurvePosition = Math.floor(Math.random() * (curveSeverity || curvePosition - (0)) +1);
        
        let lastCurve = {
          handleX: null,
          handleY: null,
          curveY: firstCurvePosition
        }
        
        if (!debug) {
          // draw the curves, along the top edge
          ctx.beginPath();
          ctx.fillStyle = background;
          ctx.moveTo(0, firstCurvePosition);
          // draw the curves
          for (let i = 0; i <= segments; i++) {
            let segmentX = segmentLength * i;
            lastCurve = this.drawCurve(ctx, curvePosition, edgePadding, segmentX, segmentX + segmentLength, lastCurve, curveSeverity, false);
          }
          ctx.lineTo(geom.width, lastCurve.curveY);
          ctx.lineTo(geom.width, geom.height);
          ctx.lineTo(0, geom.height);
          ctx.lineTo(0, firstCurvePosition);
          ctx.closePath();
          ctx.fill();
        } else {
          // draw the box
          ctx.beginPath();
          ctx.moveTo(0,0);
          ctx.lineTo(geom.width, 0);
          ctx.lineTo(geom.width, geom.height);
          ctx.lineTo(0, geom.height);
          ctx.lineTo(0, 0);
          ctx.stroke();
          ctx.closePath();
          
          // draw the curves
          for (let i = 0; i <= segments; i++) {
            let segmentX = segmentLength * i;
            lastCurve = this.drawCurve(ctx, curvePosition, edgePadding, segmentX, segmentX + segmentLength, lastCurve, curveSeverity, true);
          }
          
          // separate each segment with a vertical line
          for (let i = 1; i < segments; i++) {
            let segmentX = segmentLength * i;
            ctx.beginPath(); 
            ctx.strokeStyle = '#0000ff';
            ctx.setLineDash([4, 4]);
            ctx.moveTo(segmentX, 0);
            ctx.lineTo(segmentX, geom.height);
            ctx.stroke();
            ctx.closePath();
          }

          // draw some orientation lines, for the middle-line and their top/bottom areas in which they can move
          ctx.beginPath();
          ctx.setLineDash([4, 4]);
          ctx.moveTo(0, curvePosition);
          ctx.lineTo(geom.width, curvePosition);
          ctx.strokeStyle = '#7c400f' ;
          ctx.stroke();
          ctx.closePath();
        }
      }
    }
  );
}

              
            
!
999px

Console