Skip to content

Knob Component

The <Knob /> component is a useful tool for accepting user input for single values.

Basic Usage

Its most basic usage merely requires a v-model binding (or alternatively a :model-value and @update:model-value bindings):

vue
<script setup>
import { ref } from 'vue'
import { Knob } from 'acousti-kit'

const volume = ref(75)
</script>

<template>
  <Knob v-model="volume" />
</template>

Example

Volume: 75

Props

The following props are available for the <Knob /> component:

PropTypeDefaultOptionalNote
modelValuenumberNoUsed for two-way data binding
minnumber0YesThe minimum value of the knob
maxnumber100YesThe maximum value of the knob
tabIndexnumber0YesSets the tabindex for assistive technologies
normalStrengthnumber0YesConfigures the normal strength of mouse movement effect on value
fineStrengthnumber0YesConfigures the fine adjustment strength of mouse movement effect on value
fineKeystringAltYesSets the key used for enabling fine adjustment
captureMousebooleantrueYesEnables mouse capture when adjusting
mouseBehaviour'Flat' | 'Velocity''Flat'YesConfigures the mouse control mode

Events

The following events are available for the <Knob /> component:

PropTypeNote
@update:modelValuenumberEmitted on any value update, used for two-way data binding
@startvoidEmitted when the user starts adjusting
@endvoidEmitted when the user ends adjusting

Advanced Usage

The component comes with a couple of features that are all adjustable:

Mouse capture

Optionally, you can make the knob capture the mouse pointer to prevent it leaving the position while adjusting the knob. This can be turned on using the capture-mouse prop:

vue
<template>
  <Knob
    v-model="volume"
    :capture-mouse="true"
  />
</template>

Example:

Volume: 75

Fine Adjustment

To enable finer input control, the knob will by default scale down the intensity of change when the Alt key is held down. Both the key and the intensity can be configured:

vue
<template>
  <Knob
    v-model="volume"
    :normal-strength="10"
    :fine-strength="1"
    fine-key="Control"
  />
</template>

Example:

Volume: 75

Mouse mode

The knob supports two different kinds of mouse adjustment modes:

  1. Flat: This is the default, and changes the value with as much as the mouse moves
  2. Velocity: An alternative move where instead the velocity of the mouse is being considered

For more details on this, check the documentation of the MouseControl component which is being used internally by the <Knob />. To change to the Velocity mode, use the mouse-behaviour prop:

vue
<template>
  <Knob
    v-model="volume"
    mouse-behaviour="Velocity"
  />
</template>

Example:

Volume: 75

Specific min/max

If you have a different range than 0 to 100, you can use the min and max props to tweak the range:

vue
<template>
  <Knob
    v-model="volume"
    :min="10"
    :max="420"
  />
</template>

Example:

Volume: 75

Custom Design

The component is made to be used with a supplied component as the knob itself, and what is seen here is just the default fallback. To provide your own, you can use the slot, and the scoped props to render what you want:

vue
<template>
  <Knob
    v-slot="{ percentage, fine, active }"
    v-model="volume"
  >
    <div
      :style="{
        transform: `rotate(${(50 - percentage) * -2.75}deg)`,
        background: `hsl(${percentage}, ${active ? '100%' : '25%'}, ${fine ? '50%' : '75%'})`,
        transition: 'all .1s ease',
        height: '42px',
        width: '42px',
      }"
    />
  </Knob>
</template>

Example:

Volume: 75

Using this pattern, you can easily supply your own graphics, making knobs fit whatever style you might have. And to make that easier, there is also a <KnobAssetStack /> component that implements the common pattern where you have 3 layers:

  1. An optional background layer
  2. An optional "track" for the knob
  3. A "handle" for the knob

The component assumes all layers are the same size, and simplifies common implementations by automatically stacking them on top of each other. While the background layer remains static at all times, the track will either allow clipping, or fill up behind the handle, and the handle will rotate with the value. To ensure performance, this is being rendered in a canvas element to keep all transformations in-sync.

Volume: 75

This example is composed of the following three layers (background, track, handle) using the default clipping mode for the track to expose the background:

If you have a track asset that should instead be shown as the handle moves, you can change the track-mode of the asset stack component to Fill.

vue
<script setup>
import { ref } from 'vue'
import { Knob, KnobAssetStack } from 'acousti-kit'
import Background from '../assets/example-knob/background.svg?raw'
import Handle from '../assets/example-knob/handle.svg?raw'
import Track from '../assets/example-knob/track.svg?raw'

const volume = ref(75)
</script>

<template>
  <Knob
    v-slot="{ percentage }"
    v-model="volume"
  >
    <KnobAssetStack
      :width="128"
      :height="128"
      :min-degrees="-140"
      :max-degrees="140"
      :percentage="percentage"
      :background-svg="Background"
      :track-svg="Track"
      :handle-svg="Handle"
    />
  </Knob>
</template>

As an option to the canvas based asset stack, a simpler version exists to give more control over styling, where you can instead use slots to design the knob. Using the same assets from above, but using the <KnobComponentStack /> instead, this yields a similar looking result, but with more options to use CSS attributes for styling the different elements.

Volume: 75

The code for this stack is a bit more extensive, but mainly to cover the examples you can achieve through some rather simple CSS:

vue
<script setup>
import { ref } from 'vue'
import { Knob, KnobComponentStack } from 'acousti-kit'
import Background from '../assets/example-knob/background.svg'
import Handle from '../assets/example-knob/handle.svg'
import Track from '../assets/example-knob/track.svg'

const volume = ref(75)
</script>

<template>
  <Knob
    v-slot="{ percentage }"
    v-model="volume"
    class="knob"
  >
    <KnobComponentStack
      :min-degrees="-140"
      :max-degrees="140"
      :percentage="percentage"
    >
      <template #background>
        <Background class="background" />
      </template>
      <template #track>
        <Track class="track" />
      </template>
      <Handle class="handle" />
    </KnobComponentStack>
  </Knob>
</template>

<style scoped>
.knob {
  .background {
    width: 128px;
    height: 128px;
  }

  .handle, .handle > *, .background, .background > * {
    opacity: .5;
    transition: all .25s ease;
  }

  &:hover {
    .handle, .background {
      opacity: 1;
    }

    .handle > *, .background > :deep(path) {
      opacity: 1;
      fill: green;
    }
  }

  &:focus {
    .handle > * {
      fill: blue;
    }

    .background > :deep(path) {
      fill: red;
    }
  }
}
</style>