Menu

Brush.html.svelte component

Adds a brush component to create a range between 0 and 1. Bind to the min and max props to use them in other components. See the brushable example for use.

Param Default Required Description
min Number None
yes
The brush's min value. Useful to bind to.
max Number None
yes
The brush's max value. Useful to bind to.

Used in these examples:

SSR Examples:

<!--
  @component
  Adds a brush component to create a range between 0 and 1. Bind to the `min` and `max` props to use them in other components. See the [brushable example](https://layercake.graphcics/example/Brush) for use.
 -->
<script>
  import { clamp } from 'yootils';

  /** @type {Number} min - The brush's min value. Useful to bind to. */
  export let min;

  /** @type {Number} max - The brush's max value. Useful to bind to. */
  export let max;

  let brush;

  const p = x => {
    const { left, right } = brush.getBoundingClientRect();
    return clamp((x - left) / (right - left), 0, 1);
  };

  const handler = fn => {
    return e => {
      if (e.type === 'touchstart') {
        if (e.touches.length !== 1) return;
        e = e.touches[0];
      }

      const id = e.identifier;
      const start = { min, max, p: p(e.clientX) };

      const handle_move = e => {
        if (e.type === 'touchmove') {
          if (e.changedTouches.length !== 1) return;
          e = e.changedTouches[0];
          if (e.identifier !== id) return;
        }

        fn(start, p(e.clientX));
      };

      const handle_end = e => {
        if (e.type === 'touchend') {
          if (e.changedTouches.length !== 1) return;
          if (e.changedTouches[0].identifier !== id) return;
        } else if (e.target === brush) {
          clear();
        }

        window.removeEventListener('mousemove', handle_move);
        window.removeEventListener('mouseup', handle_end);

        window.removeEventListener('touchmove', handle_move);
        window.removeEventListener('touchend', handle_end);
      };

      window.addEventListener('mousemove', handle_move);
      window.addEventListener('mouseup', handle_end);

      window.addEventListener('touchmove', handle_move);
      window.addEventListener('touchend', handle_end);
    };
  };

  const clear = () => {
    min = null;
    max = null;
  }

  const reset = handler((start, p) => {
    min = clamp(Math.min(start.p, p), 0, 1);
    max = clamp(Math.max(start.p, p), 0, 1);
  });

  const move = handler((start, p) => {
    const d = clamp(p - start.p, -start.min, 1 - start.max);
    min = start.min + d;
    max = start.max + d;
  });

  const adjust_min = handler((start, p) => {
    min = p > start.max ? start.max : p;
    max = p > start.max ? p : start.max;
  });

  const adjust_max = handler((start, p) => {
    min = p < start.min ? p : start.min;
    max = p < start.min ? start.min : p;
  });

  $: left = 100 * min;
  $: right = 100 * (1 - max);
</script>

<div bind:this={brush} class="brush-outer" on:mousedown|stopPropagation={reset} on:touchstart|stopPropagation={reset}>
  {#if min !== null}
    <div class="brush-inner" on:mousedown|stopPropagation={move} on:touchstart|stopPropagation={move} style="left: {left}%; right: {right}%"></div>
    <div class="brush-handle" on:mousedown|stopPropagation={adjust_min} on:touchstart|stopPropagation={adjust_min} style="left: {left}%"></div>
    <div class="brush-handle" on:mousedown|stopPropagation={adjust_max} on:touchstart|stopPropagation={adjust_max} style="right: {right}%"></div>
  {/if}
</div>

<style>
  .brush-outer {
    position: relative;
    width: 100%;
    height: calc(100% + 5px);
    top: -5px;
  }

  .brush-inner {
    position: absolute;
    height: 100%;
    cursor: move;
    /* mix-blend-mode: difference; */
    background-color: #cccccc90;
    /* border: 1px solid #000; */
  }

  .brush-handle {
    position: absolute;
    width: 0;
    height: 100%;
    cursor: ew-resize;
  }

  .brush-handle::before {
    position: absolute;
    content: '';
    width: 8px;
    left: -4px;
    height: 100%;
    background: transparent;
  }
</style>