-
Notifications
You must be signed in to change notification settings - Fork 225
[Site Redesign] Logo-ticker Auto-roll with Play/Pause #6736
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
29a3388
4995a0b
aa6d357
9358e4e
451156f
3f83f71
efdd602
4a8aaa9
c73da69
7fcbf12
d2df97b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,8 +1,18 @@ | ||
| import { createTag } from '../../../utils/utils.js'; | ||
|
|
||
| const SET_COUNT = 2; | ||
| const SET_COUNT = 3; | ||
|
|
||
| function buildTrack(el, logos) { | ||
| const PLAY_SVG = '<svg class="logo-ticker-play-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M8 5l11 7-11 7z" fill="currentColor"/></svg>'; | ||
| const PAUSE_SVG = '<svg class="logo-ticker-pause-icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="8" y="5" width="3" height="14" rx="1" fill="currentColor"/><rect x="13" y="5" width="3" height="14" rx="1" fill="currentColor"/></svg>'; | ||
|
|
||
| function parseAuthoring(el) { | ||
| const rows = [...el.children]; | ||
| const logos = [...rows[0]?.querySelectorAll('span.icon') ?? []]; | ||
| const [trackLabel = '', playLabel = 'Play logos', pauseLabel = 'Pause logos'] = (rows[1]?.textContent ?? '').split('||').map((s) => s.trim()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could these strings be defined via placeholders instead? Or is GWP already aware of this new authoring pattern?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. waiting for GWP to confirm if they will take this update, but I saw that the feds placeholders sheet isn't really actively used? And putting it in a consumer's sheet feels tricky as this lives in Milo and shared by both cc and dc, so I thought that carrying the strings inside the block feels more self-contained and easier for localization. But maybe there's a separate sheet I can use? Is there another |
||
| return { logos, trackLabel, playLabel, pauseLabel }; | ||
| } | ||
|
|
||
| function buildTrack(logos, trackLabel) { | ||
| const logoSets = Array.from({ length: SET_COUNT }, (_, i) => { | ||
| const logoSet = createTag('div', { class: 'logo-ticker-set' }); | ||
| if (i > 0) logoSet.setAttribute('aria-hidden', 'true'); | ||
|
|
@@ -11,30 +21,96 @@ function buildTrack(el, logos) { | |
| }); | ||
|
|
||
| const track = createTag('div', { class: 'logo-ticker-track', role: 'img' }, logoSets); | ||
| if (el.children.length >= 2) { | ||
| track.setAttribute('aria-label', el.children[1].textContent); | ||
| } | ||
| if (trackLabel) track.setAttribute('aria-label', trackLabel); | ||
| return track; | ||
| } | ||
|
|
||
| function syncTrackMetrics(track) { | ||
| function buildPlayPauseButton(pauseLabel) { | ||
| return createTag('button', { | ||
| class: 'logo-ticker-play-pause is-playing', | ||
| 'aria-label': pauseLabel, | ||
| 'aria-pressed': 'true', | ||
| }, `${PLAY_SVG}${PAUSE_SVG}`); | ||
| } | ||
|
|
||
| function initPlayPause(button, el, playLabel, pauseLabel) { | ||
| const reducedMotionMQ = window.matchMedia('(prefers-reduced-motion: reduce)'); | ||
| let isPlaying = !reducedMotionMQ.matches; | ||
|
|
||
| const update = (playing) => { | ||
| isPlaying = playing; | ||
| el.style.setProperty('--logo-ticker-play-state', playing ? 'running' : 'paused'); | ||
| button.setAttribute('aria-label', playing ? pauseLabel : playLabel); | ||
| button.setAttribute('aria-pressed', String(playing)); | ||
| button.classList.toggle('is-playing', playing); | ||
| }; | ||
|
|
||
| button.addEventListener('click', () => update(!isPlaying)); | ||
| reducedMotionMQ.addEventListener('change', ({ matches }) => update(!matches)); | ||
|
|
||
| update(isPlaying); | ||
| } | ||
|
|
||
| function addScrollBoost(track, el, getIsPlaying) { | ||
| let targetOffset = 0; | ||
| let currentOffset = 0; | ||
| let lastScrollY = window.scrollY; | ||
| let isVisible = false; | ||
| let rafId = null; | ||
|
|
||
| new IntersectionObserver(([entry]) => { isVisible = entry.isIntersecting; }).observe(el); | ||
|
|
||
| const step = () => { | ||
| const y = window.lenis?.animatedScroll ?? window.scrollY; | ||
| const delta = y - lastScrollY; | ||
| lastScrollY = y; | ||
|
|
||
| if (getIsPlaying() && !track.classList.contains('is-static') && delta !== 0) { | ||
| const setWidth = parseFloat(track.style.getPropertyValue('--logo-ticker-set-width')) || Infinity; | ||
| targetOffset = Math.max(-setWidth, Math.min(targetOffset + delta * 0.6, setWidth)); | ||
| } | ||
|
|
||
| currentOffset += (targetOffset - currentOffset) * 0.08; | ||
| el.style.setProperty('--logo-ticker-scroll-offset', `${currentOffset}px`); | ||
|
|
||
| if (Math.abs(targetOffset - currentOffset) > 0.1) { | ||
| rafId = requestAnimationFrame(step); | ||
| } else { | ||
| rafId = null; | ||
| } | ||
| }; | ||
|
|
||
| window.addEventListener('scroll', () => { | ||
| if (!isVisible || rafId) return; | ||
| rafId = requestAnimationFrame(step); | ||
| }, { passive: true }); | ||
| } | ||
|
|
||
| function syncTrackMetrics(track, el) { | ||
| const firstSet = track.firstElementChild; | ||
| if (!firstSet) return; | ||
| const gap = parseFloat(getComputedStyle(track).columnGap) || 0; | ||
| const setWidth = firstSet.offsetWidth; | ||
| const containerWidth = track.parentElement?.clientWidth || 0; | ||
| track.classList.toggle('is-static', setWidth + 2 * gap <= containerWidth); | ||
| const isStatic = setWidth + 2 * gap <= el.clientWidth; | ||
| track.classList.toggle('is-static', isStatic); | ||
| track.style.setProperty('--logo-ticker-set-width', `${setWidth + gap}px`); | ||
| } | ||
|
|
||
| export default function init(el) { | ||
| const logos = [...el.querySelectorAll('span.icon')]; | ||
| const { logos, trackLabel, playLabel, pauseLabel } = parseAuthoring(el); | ||
| if (!logos.length) return; | ||
|
|
||
| const track = buildTrack(el, logos); | ||
| el.replaceChildren(track); | ||
| const track = buildTrack(logos, trackLabel); | ||
| const wrap = createTag('div', { class: 'logo-ticker-wrap' }); | ||
| wrap.append(track); | ||
| const button = buildPlayPauseButton(pauseLabel); | ||
| el.replaceChildren(wrap, button); | ||
|
|
||
| syncTrackMetrics(track); | ||
| const ro = new ResizeObserver(() => syncTrackMetrics(track)); | ||
| syncTrackMetrics(track, el); | ||
| const ro = new ResizeObserver(() => syncTrackMetrics(track, el)); | ||
| ro.observe(track.firstElementChild); | ||
| ro.observe(el); | ||
|
|
||
| initPlayPause(button, el, playLabel, pauseLabel); | ||
| addScrollBoost(track, el, () => button.classList.contains('is-playing')); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.