fix(player): the click layer clears only the fullscreen button's column, not a full-width band
Measured: across the full width YouTube needs only its seek bar (up to 96 px from the bottom); the 140 px is the fullscreen button's, at the right end. A full-width 140 px band handed clicks on the picture to the iframe, which then takes the keyboard focus and our shortcuts with it. The layer is now two boxes, hidden from assistive tech (a mouse surface; the keys are its shortcuts), and the tests render the big view too, where the docked bar sits under the iframe.
This commit is contained in:
@@ -6,6 +6,7 @@ import {
|
||||
useLayoutEffect,
|
||||
useRef,
|
||||
useState,
|
||||
type CSSProperties,
|
||||
} from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { createPortal } from "react-dom";
|
||||
@@ -106,13 +107,35 @@ const WIDE_PLAYER_PX = 1280;
|
||||
const SIDE_PANEL = "20rem";
|
||||
// The narrowest the stacked video gets to make room for the chrome under it (see the card's maxWidth).
|
||||
const MIN_VIDEO = "36rem";
|
||||
// YouTube's embed draws its controls at fixed CSS px from the iframe's edges, whatever its size: the
|
||||
// volume, captions and gear along the top, the fullscreen button above the seek bar 80–128 px up from
|
||||
// the bottom. The click layer's cutouts never shrink below those, else on a short player (a small window,
|
||||
// a browser zoomed to 150 % or more) the layer lies over that button and a click on it pauses instead.
|
||||
// YouTube's embed draws its controls at fixed CSS px from the iframe's edges, whatever its size
|
||||
// (measured 576×324 up to 2460×1384): the channel avatar, volume, captions and gear in a row along the
|
||||
// top (48 px), the seek bar across the full width 52–96 px up from the bottom, and the fullscreen button
|
||||
// above its right end, 80–128 px up and 24–72 px in from the right. The click layer's cutouts never
|
||||
// shrink below those, else on a short player (a small window, a browser zoomed to 150 % or more) the
|
||||
// layer lies over that button and a click on it pauses instead. Only the right-hand column needs the
|
||||
// fullscreen button's height: a full-width band that tall would hand the clicks on the picture to the
|
||||
// iframe, which takes the keyboard focus and with it our shortcuts. On a player too short for any
|
||||
// centre (under ~200 px, a phone) the layer is gone and YouTube's own touch controls take every tap.
|
||||
// Px, not rem: the text-size slider does not grow YouTube's controls.
|
||||
const YT_TOP_CONTROLS_PX = 56;
|
||||
const YT_BOTTOM_CONTROLS_PX = 140;
|
||||
const YT_SEEK_BAR_PX = 104;
|
||||
const YT_FULLSCREEN_BUTTON_PX = { above: 140, fromRight: 88 };
|
||||
// The layer is these two boxes: the picture left of the fullscreen button down to the seek bar, and the
|
||||
// column above that button.
|
||||
const YT_CLICK_LAYER: CSSProperties[] = [
|
||||
{
|
||||
left: 0,
|
||||
right: YT_FULLSCREEN_BUTTON_PX.fromRight,
|
||||
top: `max(12%, ${YT_TOP_CONTROLS_PX}px)`,
|
||||
bottom: `max(22%, ${YT_SEEK_BAR_PX}px)`,
|
||||
},
|
||||
{
|
||||
right: 0,
|
||||
width: YT_FULLSCREEN_BUTTON_PX.fromRight,
|
||||
top: `max(12%, ${YT_TOP_CONTROLS_PX}px)`,
|
||||
bottom: `max(22%, ${YT_FULLSCREEN_BUTTON_PX.above}px)`,
|
||||
},
|
||||
];
|
||||
|
||||
// The meta line's items each lead with a "·" that parts them from what stands before: the channel name
|
||||
// on its line. In the wide panel the meta has a line of its own, where the first "·" would lead nothing.
|
||||
@@ -710,34 +733,34 @@ export default function PlayerModal({
|
||||
cross-origin iframe it would be swallowed) and stops the iframe stealing keyboard focus.
|
||||
It leaves the top AND bottom edges uncovered so YouTube's edge controls stay reachable,
|
||||
measured against the iframe it sits in (in the big views that excludes our docked bar)
|
||||
and never thinner than those controls (see YT_*_CONTROLS_PX).
|
||||
and never thinner than those controls (see YT_CLICK_LAYER). A mouse surface only, so hidden
|
||||
from assistive tech: the keyboard has the shortcuts it stands for.
|
||||
Armed in the big views too (that is what keeps wheel volume working after F, the reported
|
||||
gap) — it only yields to `nativeControls`, i.e. once the user opens YouTube's own
|
||||
settings menu (focus moves into the iframe), so that menu is navigable end to end.
|
||||
Hidden on error so the "Open on YouTube" CTA is clickable. */}
|
||||
{playerError == null && (
|
||||
<div
|
||||
data-testid="yt-click-layer"
|
||||
onClick={() => {
|
||||
focusModal();
|
||||
togglePlay();
|
||||
}}
|
||||
title={t("player.shortcutsHint")}
|
||||
aria-label={t("player.shortcutsHint")}
|
||||
style={{
|
||||
top: `max(12%, ${YT_TOP_CONTROLS_PX}px)`,
|
||||
bottom: `max(22%, ${YT_BOTTOM_CONTROLS_PX}px)`,
|
||||
}}
|
||||
className={`absolute inset-x-0 z-base cursor-pointer ${
|
||||
nativeControls ? "pointer-events-none" : ""
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
{playerError == null &&
|
||||
YT_CLICK_LAYER.map((box, i) => (
|
||||
<div
|
||||
key={i}
|
||||
data-testid="yt-click-layer"
|
||||
onClick={() => {
|
||||
focusModal();
|
||||
togglePlay();
|
||||
}}
|
||||
title={t("player.shortcutsHint")}
|
||||
aria-hidden="true"
|
||||
style={box}
|
||||
className={`absolute z-base cursor-pointer ${
|
||||
nativeControls ? "pointer-events-none" : ""
|
||||
}`}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* Our own control bar for the big views (maximise / our fullscreen), where the card's
|
||||
controls are off-screen. It's IN the stage (so it survives true fullscreen, where a
|
||||
<body> portal would not) and DOCKED in its own strip: the video area above is inset by
|
||||
this bar's height (see mountRef's `bottom-14`), so the bar NEVER overlaps YouTube's seek
|
||||
this bar's height (see the iframe wrapper's `bottom-14`), so the bar NEVER overlaps YouTube's seek
|
||||
bar — that stays reachable by hovering the video's bottom edge, just above ours. Always
|
||||
visible (no auto-hide) so there's no guessing whether it will appear and no fight with the
|
||||
seek bar over the same pixels. stopPropagation so a click here doesn't toggle playback;
|
||||
|
||||
Reference in New Issue
Block a user