Instantly share code, notes, and snippets.
Created
November 30, 2023 18:46
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save reggi/cb5557437c749242c72be1ddfa99742a to your computer and use it in GitHub Desktop.
This file contains hidden or 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