Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save reggi/cb5557437c749242c72be1ddfa99742a to your computer and use it in GitHub Desktop.
Save reggi/cb5557437c749242c72be1ddfa99742a to your computer and use it in GitHub Desktop.
---
type Props = {
id?: string,
class?: string,
value?: number,
onchange?: string,
style?: string,
hideSelector?: boolean,
showSelectorOnHover?: boolean
}
---
<script>
class InputSlider extends HTMLElement {
private isDragging: boolean;
private current: HTMLElement | null;
private handle: HTMLElement | null;
private container: HTMLElement | null;
volume: number = 0
volumePct: number = 0
hideSelector?: string
showSelectorOnHover?: string
static get observedAttributes() {
return ["value"];
}
get value(): number {
return this.volume;
}
set value(newValue: number) {
if (this.volume === newValue) return;
this.volume = newValue;
this.setAttribute('value', `${newValue}`);
this.dispatchEvent(new Event('change'));
}
constructor() {
super();
this.attachShadow({ mode: 'open' });
this.isDragging = false;
this.current = null;
this.handle = null;
let value = this.getAttribute('value');
this.volume = !value ? 0 : parseFloat(this.getAttribute('value'))
if (this.volume > 1) this.volume = 1
if (this.volume < 0) this.volume = 0
this.volumePct = !value ? 0 : Math.round(this.volume * 100);
this.hideSelector = this.getAttribute('hideSelector')
this.showSelectorOnHover = this.getAttribute('showSelectorOnHover')
}
connectedCallback() {
this.container = document.createElement('div');
this.container.className = 'input-slider';
this.container.style.width = '100%';
this.container.style.display = 'inline-block';
this.container.style.height = '7px';
this.container.style.backgroundColor = '#9c9c9c';
this.container.style.position = 'relative';
this.container.style.cursor = 'pointer';
this.container.style.borderRadius = '5px';
// this.container.style.margin = '20px 0';
this.container.style.marginLeft = '5px'
this.container.style.marginRight = '5px'
this.current = document.createElement('div');
this.current.className = 'input-slider-current';
this.current.style.height = '100%';
this.current.style.backgroundColor = 'white';
this.current.style.borderRadius = '5px';
this.current.style.width = `${this.volumePct}%`;
this.container.appendChild(this.current);
this.handle = document.createElement('span');
this.handle.className = 'input-slider-handle';
this.handle.style.width = '20px';
this.handle.style.height = '20px';
this.handle.style.backgroundColor = 'white';
this.handle.style.position = 'absolute';
this.handle.style.top = '-6px';
this.handle.style.borderRadius = '50%';
this.handle.style.marginLeft = '-10px';
this.handle.style.left = `${this.volumePct}%`;
if (this.hideSelector) this.handle.style.display = 'none';
this.container.appendChild(this.handle);
this.shadowRoot.appendChild(this.container);
if (this.showSelectorOnHover) {
this.addEventListener('mouseenter', this.showHandle)
this.addEventListener('mouseleave', this.hideHandle)
}
this.addEventListener('mousedown', this.startDragging);
this.addEventListener('mousemove', this.dragging);
this.addEventListener('mouseup', this.stopDragging);
}
disconnectedCallback () {
if (this.showSelectorOnHover) {
this.addEventListener('mouseenter', this.showHandle)
this.addEventListener('mouseleave', this.hideHandle)
}
this.removeEventListener('mousedown', this.startDragging);
this.removeEventListener('mousemove', this.dragging);
this.removeEventListener('mouseup', this.stopDragging);
}
showHandle () {
if (this.handle) this.handle.style.display = 'block';
}
hideHandle () {
if (this.handle) this.handle.style.display = 'none';
}
startDragging (e: MouseEvent) {
this.isDragging = true;
this.updateVolume(e);
}
dragging (e: MouseEvent) {
if (this.isDragging) this.updateVolume(e);
}
stopDragging () {
this.isDragging = false;
}
updateVolume (e: MouseEvent) {
if (!this.current || !this.handle) return;
const rect = this.container.getBoundingClientRect();
const x = e.clientX;
let volume = (x - rect.left) / rect.width;
volume = Math.max(0, Math.min(volume, 1));
this.current.style.width = `${volume * 100}%`;
this.handle.style.left = `${volume * 100}%`;
this.value = volume;
}
}
customElements.define('input-slider', InputSlider);
class VolumeHighIcon extends HTMLElement {
height: number = 16;
width: number = 20;
constructor() {
super()
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const scale = parseFloat(this.getAttribute('scale')) || 1
const fill = this.getAttribute('fill') || 'currentColor'
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('height', (scale * this.height).toString());
svg.setAttribute('width', (scale * this.width).toString());
svg.setAttribute('viewBox', '0 0 640 512');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('fill', fill);
path.setAttribute('d', 'M533.6 32.5C598.5 85.2 640 165.8 640 256s-41.5 170.7-106.4 223.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C557.5 398.2 592 331.2 592 256s-34.5-142.2-88.7-186.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM473.1 107c43.2 35.2 70.9 88.9 70.9 149s-27.7 113.8-70.9 149c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C475.3 341.3 496 301.1 496 256s-20.7-85.3-53.2-111.8c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zm-60.5 74.5C434.1 199.1 448 225.9 448 256s-13.9 56.9-35.4 74.5c-10.3 8.4-25.4 6.8-33.8-3.5s-6.8-25.4 3.5-33.8C393.1 284.4 400 271 400 256s-6.9-28.4-17.7-37.3c-10.3-8.4-11.8-23.5-3.5-33.8s23.5-11.8 33.8-3.5zM301.1 34.8C312.6 40 320 51.4 320 64V448c0 12.6-7.4 24-18.9 29.2s-25 3.1-34.4-5.3L131.8 352H64c-35.3 0-64-28.7-64-64V224c0-35.3 28.7-64 64-64h67.8L266.7 40.1c9.4-8.4 22.9-10.4 34.4-5.3z');
svg.appendChild(path)
this.shadowRoot.appendChild(svg);
}
}
customElements.define('volume-high-icon', VolumeHighIcon);
class VolumeOffIcon extends HTMLElement {
height: number = 16;
width: number = 20;
constructor() {
super()
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const scale = parseFloat(this.getAttribute('scale')) || 1
const fill = this.getAttribute('fill') || 'currentColor'
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('height', (scale * this.height).toString());
svg.setAttribute('width', (scale * this.width).toString());
svg.setAttribute('viewBox', '0 0 640 512');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('fill', fill);
path.setAttribute('d', 'M320 64c0-12.6-7.4-24-18.9-29.2s-25-3.1-34.4 5.3L131.8 160H64c-35.3 0-64 28.7-64 64v64c0 35.3 28.7 64 64 64h67.8L266.7 471.9c9.4 8.4 22.9 10.4 34.4 5.3S320 460.6 320 448V64z');
svg.appendChild(path)
this.shadowRoot.appendChild(svg);
}
}
customElements.define('volume-off-icon', VolumeOffIcon);
class PlayIcon extends HTMLElement {
height: number = 16;
width: number = 20;
constructor() {
super()
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
const scale = parseFloat(this.getAttribute('scale')) || 1
const fill = this.getAttribute('fill') || 'currentColor'
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
svg.setAttribute('height', (scale * this.height).toString());
svg.setAttribute('width', (scale * this.width).toString());
svg.setAttribute('viewBox', '0 0 640 512');
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('fill', fill);
path.setAttribute('d', 'M73 39c-14.8-9.1-33.4-9.4-48.5-.9S0 62.6 0 80V432c0 17.4 9.4 33.4 24.5 41.9s33.7 8.1 48.5-.9L361 297c14.3-8.7 23-24.2 23-41s-8.7-32.2-23-41L73 39z');
svg.appendChild(path)
this.shadowRoot.appendChild(svg);
}
}
customElements.define('play-icon', PlayIcon);
class MuteButton extends HTMLElement {
isMute: boolean
high: HTMLElement | null
off: HTMLElement | null
container: HTMLElement | null
constructor() {
super()
this.attachShadow({ mode: 'open' });
this.isMute = Boolean(this.getAttribute('ismute')) || false
}
static copyAttributes(sourceElement, targetElement) {
for (const attr of sourceElement.attributes) {
targetElement.setAttribute(attr.name, attr.value);
}
}
connectedCallback() {
this.container = document.createElement('button')
this.container.style.background = 'none';
this.container.style.color = 'inherit';
this.container.style.border = 'none';
this.container.style.padding = '0';
this.container.style.margin = '0';
this.container.style.font = 'inherit';
this.container.style.textAlign = 'inherit';
this.container.style.cursor = 'pointer';
this.high = document.createElement('volume-high-icon');
MuteButton.copyAttributes(this, this.high)
this.high.style.display = this.isMute ? 'none' : 'inline';
this.off = document.createElement('volume-off-icon');
MuteButton.copyAttributes(this, this.off)
this.off.style.display = this.isMute ? 'inline' : 'none';
this.container.appendChild(this.high);
this.container.appendChild(this.off);
this.high.addEventListener('click', this.handleClickHigh.bind(this))
this.off.addEventListener('click', this.handleClickOff.bind(this))
this.shadowRoot.appendChild(this.container);
}
handleClickOff () {
if (!this.high || !this.off) return;
this.high.style.display = 'inline';
this.off.style.display = 'none';
this.isMute = false;
}
handleClickHigh () {
if (!this.high || !this.off) return;
console.log('click')
this.high.style.display = 'none';
this.off.style.display = 'inline';
this.isMute = true;
}
}
customElements.define('mute-button', MuteButton);
class AudioPlayer extends HTMLElement {
container: HTMLElement | null
play: HTMLElement | null
controls: HTMLElement | null
volume: HTMLElement | null
volumeContainer: HTMLElement | null
mute: HTMLElement | null
muteContainer: HTMLElement | null
constructor() {
super()
this.attachShadow({ mode: 'open' });
}
connectedCallback() {
this.container = document.createElement('div');
this.container.style.aspectRatio = '16 / 9';
this.container.style.backgroundColor = 'black';
this.container.style.color = 'white';
this.container.style.borderRadius = '20px';
this.container.style.position = 'relative';
this.controls = document.createElement('div');
this.controls.style.bottom = '0';
this.controls.style.left = '0';
this.controls.style.position = 'absolute';
this.controls.style.margin = '20px';
this.controls.style.display = 'flex';
this.container.appendChild(this.controls);
this.play = document.createElement('play-icon');
this.play.setAttribute('scale', '1.5');
this.play.style.marginTop = '3px';
this.play.style.marginRight = '13px';
this.play.style.cursor = 'pointer';
this.controls.appendChild(this.play);
this.muteContainer = document.createElement('div');
this.muteContainer.style.position = 'inline-block'
this.mute = document.createElement('mute-button');
this.mute.setAttribute('scale', '1.5');
this.mute.style.marginTop = '3px';
this.mute.style.marginRight = '13px';
this.muteContainer.appendChild(this.mute)
this.mute.addEventListener('click', () => {
if (this.volume) {
console.log('hi')
this.volume.setAttribute('value', '0')
}
})
this.volumeContainer = document.createElement('div');
this.volumeContainer.style.width = '150px'
this.volumeContainer.style.display = 'none'
this.volume = document.createElement('input-slider');
this.volume.setAttribute('value', '1')
this.volume.setAttribute('width', '150px')
this.volumeContainer.appendChild(this.volume)
this.muteContainer.append(this.volumeContainer)
this.controls.appendChild(this.muteContainer);
this.muteContainer.addEventListener('mouseover', () => {
this.volumeContainer.style.display = 'inline-block'
})
this.muteContainer.addEventListener('mouseout', () => {
this.volumeContainer.style.display = 'none'
})
this.shadowRoot.appendChild(this.container);
}
}
customElements.define('audio-player', AudioPlayer);
</script>
<input-slider {...Astro.props} value="0.4"/>
<mute-button/>
<play-icon/>
<audio-player>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment