您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows you rotate your view 90 degrees and zoom in on neal.fun/internet-roadtrip
当前为
// ==UserScript== // @name Internet Roadtrip - Look Out The Window v1 // @description Allows you rotate your view 90 degrees and zoom in on neal.fun/internet-roadtrip // @namespace me.netux.site/user-scripts/internet-roadtrip/look-out-the-window-v1 // @version 1.11 // @author Netux // @license MIT // @match https://neal.fun/internet-roadtrip/ // @icon https://neal.fun/favicons/internet-roadtrip.png // @grant none // @run-at document-end // @require https://cdn.jsdelivr.net/npm/[email protected] // ==/UserScript== (async () => { const STORAGE_KEY = "internet-roadtrip/mod/look-out-the-window"; const containerEl = document.querySelector('.container'); const Direction = Object.freeze({ AHEAD: 0, RIGHT: 1, BACK: 2, LEFT: 3 }); const state = { lookingDirection: Direction.AHEAD, zoom: 1, dom: {} }; if (STORAGE_KEY in localStorage) { Object.assign( state, JSON.parse(localStorage.getItem(STORAGE_KEY)) ); } function setupDom() { injectStylesheet(); const containerEl = document.querySelector('.container'); state.dom.containerEl = containerEl; state.dom.panoIframeEls = Array.from(containerEl.querySelectorAll('[id^="pano"]')); state.dom.windowEl = document.createElement('div'); state.dom.windowEl.className = 'window'; state.dom.panoIframeEls.at(-1).insertAdjacentElement('afterend', state.dom.windowEl); function lookRight() { state.lookingDirection = (state.lookingDirection + 1) % 4; updateLookAt(); storeSettings(); } function lookLeft() { state.lookingDirection = state.lookingDirection - 1; if (state.lookingDirection < 0) { state.lookingDirection = 3; } updateLookAt(); storeSettings(); } function chevronImage(rotation) { const imgEl = document.createElement('img'); imgEl.src = '/sell-sell-sell/arrow.svg'; // yoink imgEl.style.width = `10px`; imgEl.style.aspectRatio = `1`; imgEl.style.filter = `invert(1)`; imgEl.style.rotate = `${rotation}deg`; return imgEl; } state.dom.lookLeftButtonEl = document.createElement('button'); state.dom.lookLeftButtonEl.className = 'look-left-btn'; state.dom.lookLeftButtonEl.appendChild(chevronImage(90)); state.dom.lookLeftButtonEl.addEventListener('click', lookLeft); containerEl.appendChild(state.dom.lookLeftButtonEl); state.dom.lookRightButtonEl = document.createElement('button'); state.dom.lookRightButtonEl.className = 'look-right-btn'; state.dom.lookRightButtonEl.appendChild(chevronImage(-90)); state.dom.lookRightButtonEl.addEventListener('click', lookRight); containerEl.appendChild(state.dom.lookRightButtonEl); window.addEventListener("keydown", (event) => { switch (event.key) { case "ArrowLeft": { lookLeft(); break; } case "ArrowRight": { lookRight(); break; } } }); window.addEventListener("wheel", (event) => { if (event.target !== document.documentElement) { // pointing at nothing but the backdrop return; } const scrollingForward = event.deltaY < 0; state.zoom = Math.min(Math.max(1, state.zoom * (scrollingForward ? 1.1 : 0.9)), 20); updateZoom(); storeSettings(); }) updateLookAt(); updateZoom(); } function injectStylesheet() { const styleEl = document.createElement('style'); styleEl.innerText = ` .container { & .look-right-btn, & .look-left-btn { position: fixed; bottom: 200px; transform: translateY(-50%); padding-block: 1.5rem; border: none; background-color: whitesmoke; cursor: pointer; } & .look-right-btn { right: 0; padding-inline: 0.35rem 0.125rem; border-radius: 15px 0 0 15px; } & .look-left-btn { left: 0; padding-inline: 0.125rem 0.25rem; border-radius: 0 15px 15px 0; } &:not([data-looking-direction="0"]) :is(.wheel-container, .options) { display: none; } & .window { position: fixed; width: 100%; background-image: url("https://cloudy.netux.site/neal_internet_roadtrip/side window.png"); background-size: cover; height: 100%; background-position: center; pointer-events: none; &.window--flip { rotate: y 180deg; } &.window--back { transform-origin: center 20%; background-image: url("https://cloudy.netux.site/neal_internet_roadtrip/back window.png"); } } & [id^="pano"], & window { transition: opacity 300ms linear, scale 100ms linear; } } `; document.head.appendChild(styleEl); } function patch(vue) { function replaceHeadingInPanoUrl(urlStr, headingOverride) { if (!urlStr) { return urlStr; } headingOverride ??= vue.state.currentHeading; const url = new URL(urlStr); url.searchParams.set('heading', (headingOverride + state.lookingDirection * 90) % 360); return url.toString(); } vue.state.getPanoUrl = new Proxy(vue.methods.getPanoUrl, { apply(ogGetPanoUrl, thisArg, args) { const urlStr = ogGetPanoUrl.apply(thisArg, args); return replaceHeadingInPanoUrl(urlStr, this.currentHeading); } }); const panoEls = Object.keys(vue.$refs).filter((name) => name.startsWith('pano')).map((key) => vue.$refs[key]); state.transitionPano = (animate = true) => { const currFrame = vue.state.currFrame; const nextFrame = (currFrame + 1) % panoEls.length; const activePanoEl = panoEls[currFrame]; if (animate) { const transitionPanoEl = panoEls[nextFrame]; vue.state.currFrame = nextFrame; transitionPanoEl.src = replaceHeadingInPanoUrl(activePanoEl.src); setTimeout(() => { vue.methods.switchFrameOrder(); }, 500); } else { activePanoEl.src = replaceHeadingInPanoUrl(activePanoEl.src); } }; } function updateLookAt(animate = true) { state.dom.containerEl.dataset.lookingDirection = state.lookingDirection; const isLookingAhead = state.lookingDirection === Direction.AHEAD; state.dom.windowEl.style.display = isLookingAhead ? 'none' : ''; if (!isLookingAhead) { state.dom.windowEl.classList.toggle('window--flip', state.lookingDirection === Direction.LEFT); state.dom.windowEl.classList.toggle('window--back', state.lookingDirection === Direction.BACK); } state.transitionPano(animate); } function updateZoom() { for (const panoIframeEl of state.dom.panoIframeEls) { panoIframeEl.style.scale = (state.zoom * 0.4 + 0.6 /* parallax */).toString(); } state.dom.windowEl.style.scale = state.zoom.toString(); } function storeSettings() { localStorage.setItem(STORAGE_KEY, JSON.stringify({ lookingDirection: state.lookingDirection, zoom: state.zoom })); } state.vue = await IRF.vdom.container; patch(state.vue); setupDom(); })();