Instantly share code, notes, and snippets.
Created
November 30, 2023 18:46
-
Save reggi/cb5557437c749242c72be1ddfa99742a to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--- | |
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