为移动端 X 网页视频添加独立全屏按钮,点击可调用浏览器原生播放器进行全屏播放。Add a native fullscreen button for videos on mobile X.com.
// ==UserScript==
// @name X Mobile Native Fullscreen Video Button
// @name:zh-CN X 移动端原生视频全屏按钮
// @namespace https://greasyfork.org/users/byd-cn
// @version 1.6
// @description 为移动端 X 网页视频添加独立全屏按钮,点击可调用浏览器原生播放器进行全屏播放。Add a native fullscreen button for videos on mobile X.com.
// @author byd-cn
// @license MIT
// @match https://x.com/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function () {
'use strict';
const addButton = (container) => {
if (container.querySelector('.custom-fullscreen-btn')) return;
const btn = document.createElement('button');
btn.className = 'custom-fullscreen-btn';
btn.innerHTML = `
<svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.1" stroke-linecap="round" stroke-linejoin="round">
<path d="M4 9V4h5"/>
<path d="M20 9V4h-5"/>
<path d="M4 15v5h5"/>
<path d="M20 15v5h-5"/>
</svg>
`;
Object.assign(btn.style, {
position: 'fixed',
top: '45%',
right: '16px',
zIndex: '99999',
background: 'rgba(0, 0, 0, 0.35)',
border: 'none',
borderRadius: '50%',
width: '38px',
height: '38px',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
cursor: 'pointer',
padding: '0',
boxShadow: 'none',
transition: 'opacity 0.2s',
opacity: '0.92'
});
btn.addEventListener('mouseenter', () => { btn.style.opacity = '1'; });
btn.addEventListener('mouseleave', () => { btn.style.opacity = '0.92'; });
const handleClick = (e) => {
e.stopImmediatePropagation();
e.stopPropagation();
e.preventDefault();
const wrap = container.querySelector('div:first-child > div');
const video = wrap ? wrap.querySelector('video') : null;
if (!video) {
const player = container.closest('[data-testid="videoPlayer"]') || container;
if (player.requestFullscreen) player.requestFullscreen();
else if (player.webkitRequestFullscreen) player.webkitRequestFullscreen();
return;
}
console.log('[X Fullscreen Button] Enter fullscreen');
document.querySelectorAll('video').forEach(v => {
if (v !== video) {
v.pause();
v.muted = true;
}
});
video.controls = true;
video.removeAttribute('disablepictureinpicture');
video.controlsList = '';
video.muted = false;
if (video.requestFullscreen) {
video.requestFullscreen().catch(() => {});
} else if (video.webkitRequestFullscreen) {
video.webkitRequestFullscreen().catch(() => {});
}
video.play().catch(() => {});
};
btn.addEventListener('click', handleClick, { capture: true });
btn.addEventListener('touchend', handleClick, { capture: true, passive: false });
container.style.position = 'relative';
container.appendChild(btn);
};
const observer = new MutationObserver(() => {
const list = document.querySelectorAll('div[data-testid="videoComponent"]:not(.x-custom-enhanced)');
list.forEach(container => {
container.classList.add('x-custom-enhanced');
addButton(container);
});
});
observer.observe(document.body, { childList: true, subtree: true });
const init = () => {
const list = document.querySelectorAll('div[data-testid="videoComponent"]');
list.forEach(container => {
if (!container.classList.contains('x-custom-enhanced')) {
container.classList.add('x-custom-enhanced');
addButton(container);
}
});
};
if (document.readyState === 'loading') {
window.addEventListener('load', init);
} else {
init();
}
})();