Welcome to web-knobsv0.0.4
Cross-framework audio component library!
Cross-framework audio component library!
npm i @eyewave/web-knobsyarn add @eyewave/web-knobspnpm add @eyewave/web-knobsbun add @eyewave/web-knobs Basic styled svg knob
<script>
import { SvgKnob } from '@eyewave/web-knobs/svelte';
let value = $state(0.0);
</script>
<div>
<SvgKnob bind:value />
<span>{value.toFixed(2)}</span>
<SvgKnob bind:value disabled />
</div>
<style>
div {
color: #22bfee;
}
</style>import React, { useState } from 'react';
import { SvgKnob } from '@eyewave/web-knobs/react';
export default function () {
const [value, setValue] = useState(0.0);
return (
<div style={{ color: '#22bfee' }}>
<SvgKnob value={value} onValueChange={setValue} />
<span>{value.toFixed(2)}</span>
<SvgKnob value={value} disabled />
</div>
);
}<template>
<div>
<SvgKnob v-model="value" />
<span>{{ value.toFixed(2) }}</span>
<SvgKnob v-model="value" :disabled="true" />
</div>
</template>
<script>
import { ref } from 'vue';
import SvgKnob from '@eyewave/web-knobs/vue';
export default {
components: { SvgKnob },
setup() {
const value = ref(0.0);
return { value };
}
};
</script>
<style scoped>
div {
color: #22bfee;
}
</style> You can use an image strip for a knob too.
<script lang="ts">
import { ImageKnob } from '@eyewave/web-knobs/svelte';
let value = $state(0.0);
</script>
<ImageKnob bind:value src="/web-knobs/PurpleKnob2.webp" width={90} height={90} />import React, { useState } from 'react';
import { ImageKnob } from '@eyewave/web-knobs/react';
export default function () {
const [value, setValue] = useState(0.0);
return (
<ImageKnob
value={value}
onValueChange={setValue}
src="/web-knobs/PurpleKnob2.webp"
width={90}
height={90}
/>
);
}<template>
<ImageKnob v-model="value" src="/web-knobs/PurpleKnob2.webp" :width="90" :height="90" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { ImageKnob } from '@eyewave/web-knobs/vue';
const value = ref(0.0);
</script> In reality, svgknob and image knob are both based from a {' component, that you can use as well, creating your very own custom made knob.
<script lang="ts">
import { Draggable } from '@eyewave/web-knobs/svelte';
import { valueToAngle } from '@eyewave/web-knobs/core/helpers';
let value = $state(0.5);
let angle = $derived(valueToAngle(value, -135, 135));
</script>
<Draggable bind:value>
<div class="knob">
<div class="glow" style="filter: blur({value * 5}px);transform: scale({value})"></div>
<div class="thumb" style="transform: rotate({angle}deg) translateY(-10px)"></div>
<span>{value.toFixed(2)}</span>
</div>
</Draggable>
<style>
.knob {
z-index: 0;
user-select: none;
width: 60px;
height: 60px;
border-radius: 100px;
background: #000;
position: relative;
display: grid;
place-items: center;
}
.glow,
.thumb {
z-index: -1;
position: absolute;
background: red;
border-radius: 30px;
}
.glow {
width: 30px;
height: 30px;
}
.thumb {
width: 10px;
height: 20px;
}
</style>import React, { useState, useMemo } from 'react';
import { Draggable } from '@eyewave/web-knobs/react';
import { valueToAngle } from '@eyewave/web-knobs/core/helpers';
import styles from './CustomKnob.module.css';
export default function () {
const [value, setValue] = useState(0.5);
const angle = useMemo(() => valueToAngle(value, -135, 135), [value]);
return (
<Draggable value={value} onValueChange={setValue}>
<div className={styles.knob}>
<div
className={styles.glow}
style={{
filter: `blur(${value * 5}px)`,
transform: `scale(${value})`
}}
></div>
<div
className={styles.thumb}
style={{
transform: `rotate(${angle}deg) translateY(-10px)`
}}
></div>
<span>{value.toFixed(2)}</span>
</div>
</Draggable>
);
}<template>
<KnobDraggable v-model="value">
<div class="knob">
<div
class="glow"
:style="{
filter: `blur(${value * 5}px)`,
transform: `scale(${value})`
}"
></div>
<div
class="thumb"
:style="{
transform: `rotate(${angle}deg) translateY(-10px)`
}"
></div>
<span>{{ value.toFixed(2) }}</span>
</div>
</KnobDraggable>
</template>
<script setup>
import { ref, computed } from 'vue';
import { Draggable as KnobDraggable } from '@eyewave/web-knobs/vue';
import { valueToAngle } from '@eyewave/web-knobs/core/helpers';
const value = ref(0.5);
const angle = computed(() => valueToAngle(value.value, -135, 135));
</script>
<style scoped>
.knob {
z-index: 0;
user-select: none;
width: 60px;
height: 60px;
border-radius: 100px;
background: #000;
position: relative;
display: grid;
place-items: center;
}
.glow,
.thumb {
z-index: -1;
position: absolute;
background: red;
border-radius: 30px;
}
.glow {
width: 30px;
height: 30px;
}
.thumb {
width: 10px;
height: 20px;
}
</style> Since all knobs operate on values between 0 and 1 any type of scaling is done outside of the scope of a knob with parameter objects.
<script lang="ts">
import { LinearParam, LogParam } from '@eyewave/web-knobs/core/params';
import { SvgKnob } from '@eyewave/web-knobs/svelte';
let value = $state(0.0);
const freqParam = new LogParam(20, 20_000, 10);
const linParam = new LinearParam(0, 100);
</script>
<section>
<div>
<SvgKnob bind:value />
<span>{freqParam.denormalize(value) | 0}hz</span>
</div>
<div>
<SvgKnob bind:value />
<span>{linParam.denormalize(value) | 0}%</span>
</div>
</section>
<style>
section {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
</style>import React, { useState } from 'react';
import { SvgKnob } from '@eyewave/web-knobs/react';
import { LinearParam, LogParam } from '@eyewave/web-knobs/core/params';
export default function () {
const [value, setValue] = useState(0.0);
const freqParam = new LogParam(20, 20_000, 10);
const linParam = new LinearParam(0, 100);
return (
<section style={{ display: 'flex', gap: '0.5rem', flexWrap: 'wrap' }}>
<div>
<SvgKnob value={value} onValueChange={setValue} />
<span>{Math.floor(freqParam.denormalize(value))}hz</span>
</div>
<div>
<SvgKnob value={value} onValueChange={setValue} />
<span>{Math.floor(linParam.denormalize(value))}%</span>
</div>
</section>
);
}<template>
<section>
<div>
<SvgKnob v-model="value" />
<span>{{ Math.floor(freqParam.denormalize(value)) }} Hz</span>
</div>
<div>
<SvgKnob v-model="value" />
<span>{{ Math.floor(linParam.denormalize(value)) }}%</span>
</div>
</section>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { LinearParam, LogParam } from '@eyewave/web-knobs/core/params';
import { SvgKnob } from '@eyewave/web-knobs/vue';
const value = ref(0.0);
const freqParam = new LogParam(20, 20000, 10);
const linParam = new LinearParam(0, 100);
</script>
<style scoped>
section {
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
</style> You can specify snap points and how strong the snapping is for your knob. The knob will automatically sort and insert 0 and 1 to your snap point list, so [0.6,0.3] will become [0.0,0.3,0.6,1.0].
When snapPoints are specified, arrow keys on the keyboard will make the knob jump between them. Pressing alt key will disable the snapping.
This concept will be importand later in the next example.
<script>
import { SvgKnob } from '@eyewave/web-knobs/svelte';
let value = $state(0.0);
</script>
<div>
<SvgKnob bind:value snapPoints={[0.5]} />
<span>{value.toFixed(2)}</span>
</div>import React, { useState } from 'react';
import { SvgKnob } from '@eyewave/web-knobs/react';
export default function SnapKnob() {
const [value, setValue] = useState(0.0);
return (
<div>
<SvgKnob value={value} onValueChange={setValue} snapPoints={[0.5]} />
<span>{value.toFixed(2)}</span>
</div>
);
}<template>
<div>
<SvgKnob v-model="value" :snapPoints="[0.5]" />
<span>{{ value.toFixed(2) }}</span>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { SvgKnob } from '@eyewave/web-knobs/vue';
const value = ref(0.0);
</script> Enums, or in typescript realm readonly string[] parameter are a special type of parameter that don't denormalize into a number, instead into a string. EnumParam class comes with helpful properties for knob ui with already calcualted snap points and snap threshold to make value changes 'instant'.
🍍
Low pass
false
<script lang="ts">
import { BoolParam, EnumParam } from '@eyewave/web-knobs/core/params';
import { SvgKnob } from '@eyewave/web-knobs/svelte';
let value = $state(0);
const fruitParam = new EnumParam(['🍍', '🍉', '🍌', '🍋', '🍇'] as const);
const filterTypeParam = new EnumParam([
'Low pass',
'High pass',
'Low shelf',
'High shelf',
'Bell',
'Notch',
'Allpass'
] as const);
const booleanParam = new BoolParam();
</script>
<SvgKnob bind:value snapPoints={fruitParam.snapPoints} snapThreshold={fruitParam.snapThreshold} />
<p>{fruitParam.denormalize(value)}</p>
<SvgKnob bind:value {...filterTypeParam.knobProps} />
<p>{filterTypeParam.denormalize(value)}</p>
<SvgKnob bind:value {...booleanParam.knobProps} />
<p>{booleanParam.denormalize(value)}</p>import React, { useState } from 'react';
import { SvgKnob } from '@eyewave/web-knobs/react';
import { BoolParam, EnumParam } from '@eyewave/web-knobs/core/params';
export default function () {
const [value, setValue] = useState(0);
const fruitParam = new EnumParam(['🍍', '🍉', '🍌', '🍋', '🍇'] as const);
const filterTypeParam = new EnumParam([
'Low pass',
'High pass',
'Low shelf',
'High shelf',
'Bell',
'Notch',
'Allpass'
] as const);
const booleanParam = new BoolParam();
return (
<div>
<SvgKnob
value={value}
onValueChange={setValue}
snapPoints={fruitParam.snapPoints}
snapThreshold={fruitParam.snapThreshold}
/>
<p>{fruitParam.denormalize(value)}</p>
<SvgKnob value={value} onValueChange={setValue} {...filterTypeParam.knobProps} />
<p>{filterTypeParam.denormalize(value)}</p>
<SvgKnob value={value} onValueChange={setValue} {...booleanParam.knobProps} />
<p>{booleanParam.denormalize(value)}</p>
</div>
);
}<template>
<SvgKnob
v-model="value"
:snapPoints="fruitParam.snapPoints"
:snapThreshold="fruitParam.snapThreshold"
/>
<p>{{ fruitParam.denormalize(value) }}</p>
<SvgKnob v-model="value" v-bind="filterTypeParam.knobProps" />
<p>{{ filterTypeParam.denormalize(value) }}</p>
<SvgKnob v-model="value" v-bind="booleanParam.knobProps" />
<p>{{ booleanParam.denormalize(value) }}</p>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { BoolParam, EnumParam } from '@eyewave/web-knobs/core/params';
import { SvgKnob } from '@eyewave/web-knobs/vue';
const value = ref(0);
const fruitParam = new EnumParam(['🍍', '🍉', '🍌', '🍋', '🍇'] as const);
const filterTypeParam = new EnumParam([
'Low pass',
'High pass',
'Low shelf',
'High shelf',
'Bell',
'Notch',
'Allpass'
] as const);
const booleanParam = new BoolParam();
</script>