Last active
June 16, 2025 14:04
-
-
Save lunamoth/a0d3eb63eafbe65e89c6947676aa4a29 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
// ==UserScript== | |
// @name 범용 영상 회전기 (단축키 전용) | |
// @version 1.5 | |
// @description 단축키(Ctrl+Shift+Alt+R)로 모든 웹사이트의 HTML5 영상을 90도씩 순차적으로 회전시킵니다. | |
// @author Gemini | |
// @match *://*/* | |
// @grant none | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
class VideoRotator { | |
static #CONFIG = { | |
ROTATION_STEPS: 4, | |
ROTATION_ANGLE_DEGREES: 90, | |
SHORTCUT_KEY: 'KeyR', | |
VIDEO_SELECTORS: ['video:hover', 'video:not([paused])', 'video'], | |
EDITABLE_TAGS: new Set(['INPUT', 'TEXTAREA']), | |
}; | |
#rotationState = new WeakMap(); | |
constructor() { | |
document.addEventListener('keydown', this.#handleKeyDown.bind(this), true); | |
} | |
#isShortcutPressed(e) { | |
return e.code === VideoRotator.#CONFIG.SHORTCUT_KEY && e.ctrlKey && e.shiftKey && e.altKey; | |
} | |
#isTargetEditable(target) { | |
return target.isContentEditable || VideoRotator.#CONFIG.EDITABLE_TAGS.has(target.tagName); | |
} | |
#findPrioritizedVideo() { | |
for (const selector of VideoRotator.#CONFIG.VIDEO_SELECTORS) { | |
const videoElement = document.querySelector(selector); | |
if (videoElement) return videoElement; | |
} | |
return null; | |
} | |
#calculateTransform(step, video) { | |
if (step === 0) return ''; | |
const angle = step * VideoRotator.#CONFIG.ROTATION_ANGLE_DEGREES; | |
let transform = `rotate(${angle}deg)`; | |
const isPortrait = step % 2 !== 0; | |
if (isPortrait) { | |
const { clientWidth, clientHeight } = video; | |
if (clientWidth > 0 && clientHeight > 0) { | |
const scale = Math.min(clientWidth / clientHeight, clientHeight / clientWidth); | |
transform += ` scale(${scale})`; | |
} | |
} | |
return transform; | |
} | |
#applyRotation(video) { | |
const currentStep = this.#rotationState.get(video) ?? 0; | |
const nextStep = (currentStep + 1) % VideoRotator.#CONFIG.ROTATION_STEPS; | |
const transformStyle = this.#calculateTransform(nextStep, video); | |
video.style.transform = transformStyle; | |
video.style.willChange = transformStyle ? 'transform' : 'auto'; | |
if (nextStep === 0) { | |
this.#rotationState.delete(video); | |
} else { | |
this.#rotationState.set(video, nextStep); | |
} | |
} | |
#handleKeyDown(event) { | |
if (!this.#isShortcutPressed(event) || this.#isTargetEditable(event.target)) { | |
return; | |
} | |
event.preventDefault(); | |
event.stopPropagation(); | |
const targetVideo = this.#findPrioritizedVideo(); | |
if (targetVideo) { | |
this.#applyRotation(targetVideo); | |
} | |
} | |
} | |
new VideoRotator(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment