Skip to content

Instantly share code, notes, and snippets.

@Jacbo1
Last active November 21, 2025 16:42
Show Gist options
  • Select an option

  • Save Jacbo1/e750aa7d09cf63ed9e79464bab86d405 to your computer and use it in GitHub Desktop.

Select an option

Save Jacbo1/e750aa7d09cf63ed9e79464bab86d405 to your computer and use it in GitHub Desktop.
Twitter Background
// ==UserScript==
// @name Twitter Background
// @namespace http://tampermonkey.net/
// @version 2024-03-23
// @description Give Twitter background images
// @author You
// @match *://*.twitter.com/*
// @match *://*.x.com/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=twitter.com
// @grant none
// ==/UserScript==
// https://gist.github.com/Jacbo1/e750aa7d09cf63ed9e79464bab86d405
(function() {
'use strict';
// ##################### SETTINGS #####################
const repeats = 1
const fadeInterval = 60 * 1000
const fadeDuration = 1 * 1000
const localStorageKey = "adnfhufsjmidsigfsjmdafiu twitter bg img"
const localStorageTimeKey = "adnfhufsjmidsigfsjmdafiu twitter bg img time"
const localStorageIndexKey = "adnfhufsjmidsigfsjmdafiu twitter bg img index"
const localStorageIndexActiveKey = "adnfhufsjmidsigfsjmdafiu twitter bg img active"
const localStorageIndexInactiveKey = "adnfhufsjmidsigfsjmdafiu twitter bg img inactive"
// Add images here. Only images from Twitter seem to load
const images = [
'https://pbs.twimg.com/media/GDmNHmvXIAIT0cp?format=jpg&name=orig', // https://fxtwitter.com/godonnellart/status/1745581527457579392/photo/1
{
// https://fxtwitter.com/kamillustrator/status/1950068655435235331/photo/4
url: 'https://pbs.twimg.com/media/GxAJG3vbUAA11Cb?format=jpg&name=orig',
},
/*{
// <tweet URL>
url: '<twitter image url>',
fit: true, // Fit instead of fill
bgcolor: '#F6A450',
pos: '0% 50%' // left, vertically centered
},*/
]
// ####################################################
// ####################################################
const style = document.createElement('style');
const scrollFraction = 0.5
const scrollMin = 50 - 50 * scrollFraction
const scrollMax = 50 + 50 * scrollFraction
const scrollDuration = (fadeInterval + fadeDuration) / 1000 + 1
style.innerHTML = `
@keyframes backgroundScrollUp {
0% {
background-position-y: ${scrollMax}%;
}
100% {
background-position-y: ${scrollMin}%;
}
}
@keyframes backgroundScrollDown {
0% {
background-position-y: ${scrollMin}%;
}
100% {
background-position-y: ${scrollMax}%;
}
}
@keyframes backgroundScrollLeft {
0% {
background-position-x: ${scrollMax}%;
}
100% {
background-position-x: ${scrollMin}%;
}
}
@keyframes backgroundScrollRight {
0% {
background-position-x: ${scrollMin}%;
}
100% {
background-position-x: ${scrollMax}%;
}
}
.background-scroll-up {
animation: backgroundScrollUp ${scrollDuration}s linear forwards;
}
.background-scroll-down {
animation: backgroundScrollDown ${scrollDuration}s linear forwards;
}
.background-scroll-left {
animation: backgroundScrollLeft ${scrollDuration}s linear forwards;
}
.background-scroll-right {
animation: backgroundScrollRight ${scrollDuration}s linear forwards;
}
`
document.head.appendChild(style);
function removeScrollClasses(div) {
div.classList.remove('background-scroll-up')
div.classList.remove('background-scroll-down')
div.classList.remove('background-scroll-left')
div.classList.remove('background-scroll-right')
}
function addRandomScrollClass(div, imageUrl) {
removeScrollClasses(div)
const img = new Image()
img.src = imageUrl
img.onload = function() {
const xratio = img.naturalWidth / window.innerWidth
const yratio = img.naturalHeight / window.innerHeight
if (xratio > yratio) {
// Scroll width
if (Math.random() > 0.5) div.classList.add('background-scroll-left')
else div.classList.add('background-scroll-right')
} else {
// Scroll height
if (Math.random() > 0.5) div.classList.add('background-scroll-up')
else div.classList.add('background-scroll-down')
}
}
}
// ####################################################
// ##################### INIT #####################
for (let i = 0; i < images.length; i++) {
const img = images[i];
const regex = /^(https:\/\/pbs\.twimg\.com\/media\/[a-zA-Z0-9-_]+\?format=\w+&name=)(?!orig).+$/;
if (typeof img === 'string') {
const match = regex.exec(img);
if (match) {
images[i] = match[1] + 'orig';
}
} else if (img.url) {
const match = regex.exec(img.url);
if (match) {
img.url = match[1] + 'orig';
}
}
}
let indexes = []
let newImage = Math.floor(Math.random() * indexes.length)
let curImage = newImage
let fadeTime = Date.now()
let docVisible = document.visibilityState === 'visible'
let docFocused = document.hasFocus()
let shouldRun = docVisible && docFocused
const backgroundDivs = [
document.createElement('div'),
document.createElement('div')
]
for (let i = 0; i < 2; i++) {
const div = backgroundDivs[i];
// Set styles for the div
div.style.position = 'fixed';
div.style.top = '0';
div.style.left = '0';
div.style.width = '100%';
div.style.height = '100%';
div.style.zIndex = -9999 + i;
div.style.backgroundSize = 'cover';
div.style.backgroundPosition = 'center';
div.style.backgroundRepeat = 'no-repeat'
// Append the div to the body
document.body.appendChild(div);
}
let backDiv = backgroundDivs[0]
let frontDiv = backgroundDivs[1]
// ################################################
// ##################### IMAGE CHANGING #####################
function applyImage(div, index) {
const image = images[index]
let url
if (typeof image === 'string') {
// Simple URL
url = image
div.style.backgroundSize = 'cover';
div.style.backgroundPosition = 'center';
} else {
url = image.url
if (image.bgcolor) div.style.backgroundColor = image.bgcolor
div.style.backgroundSize = image.fit ? 'contain' : 'cover'
div.style.backgroundPosition = image.pos ?? 'center'
}
div.style.backgroundImage = `linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("${url}")`;
if (div === frontDiv && !image.pos) addRandomScrollClass(div, url)
}
function refreshIndexes() {
for (let i = 0; i < images.length; i++) {
for (let j = 0; j < repeats; j++) {
indexes.push(i)
}
}
}
function getIndex(remove = true) {
if (indexes.length === 0) {
refreshIndexes()
}
const i = Math.floor(Math.random() * indexes.length)
const index = indexes[i]
if (remove) {
indexes.splice(i, 1)
}
if (indexes.length === 0) {
refreshIndexes()
}
return index
}
let firstSet = true
function setImageTo(newIndex) {
console.log(`New image: ${getImageUrl(images[newIndex])}`);
curImage = newImage
newImage = newIndex
if (firstSet) {
firstSet = false
curImage = newImage
}
backDiv.style.transition = '';
frontDiv.style.transition = '';
applyImage(backDiv, curImage)
applyImage(frontDiv, newImage)
}
let divSwitch = true
function fadeImageTo(newIndex) {
console.log(`New image: ${getImageUrl(images[newIndex])}`);
curImage = newImage
newImage = newIndex
if (firstSet) {
firstSet = false
curImage = newImage
}
divSwitch = !divSwitch
backDiv;
frontDiv;
if (divSwitch) {
backDiv = backgroundDivs[0]
frontDiv = backgroundDivs[1]
} else {
backDiv = backgroundDivs[1]
frontDiv = backgroundDivs[0]
}
backDiv.style.zIndex = '-9999';
frontDiv.style.zIndex = '-9998';
backDiv.style.transition = '';
frontDiv.style.transition = '';
backDiv.style.opacity = 1
frontDiv.style.opacity = 0
backDiv.style.transition = `opacity ${fadeDuration / 1000}s`;
frontDiv.style.transition = `opacity ${fadeDuration / 1000}s`;
backDiv.style.opacity = 0
frontDiv.style.opacity = 1
applyImage(backDiv, curImage)
applyImage(frontDiv, newImage)
fadeTime = Date.now()
saveTime()
}
// ##########################################################
// ##################### SAVING AND LOADING #####################
function getImageUrl(arrayItem) {
if (typeof arrayItem === 'string') return arrayItem
return arrayItem.url
}
function saveTime() {
localStorage.setItem(localStorageTimeKey, JSON.stringify(fadeTime))
}
function loadTime() {
try {
let time = localStorage.getItem(localStorageTimeKey)
if (!time) return false;
time = JSON.parse(time)
const now = Date.now()
if (now - time < fadeDuration + fadeInterval) {
fadeTime = time
return true
}
} catch {}
return false
}
function saveActiveTime() {
localStorage.setItem(localStorageIndexActiveKey, JSON.stringify(Date.now()))
}
function loadActiveTime() {
const data = localStorage.getItem(localStorageIndexActiveKey)
if (!data) return Date.now()
return JSON.parse(data)
}
function saveInactiveTime() {
localStorage.setItem(localStorageIndexInactiveKey, JSON.stringify(Date.now()))
}
function loadInactiveTime() {
const data = localStorage.getItem(localStorageIndexInactiveKey)
if (!data) return Date.now()
return JSON.parse(data)
}
function saveIndexes() {
const imageUrls = []
for (const image of images) {
imageUrls.push(getImageUrl(image))
}
localStorage.setItem(localStorageKey, JSON.stringify({
images: imageUrls,
indexes: indexes,
curIndex: newImage
}))
}
function loadIndexes() {
try {
const obj = JSON.parse(localStorage.getItem(localStorageKey))
const objIndexes = obj.indexes
const objImages = obj.images
const imageMap = new Map()
for (let i = 0; i < images.length; i++) {
imageMap.set(getImageUrl(images[i]), i)
}
for (let i = objIndexes.length - 1; i >= 0; i--) {
const newIndex = imageMap.get(objImages[objIndexes[i]])
if (newIndex) {
objIndexes[i] = newIndex
} else {
objIndexes.splice(i, 1)
}
}
let curIndex = imageMap.get(objImages[obj.curIndex])
const objImageSet = new Set(objImages)
for (let i = 0; i < images.length; i++) {
if (!objImageSet.has(getImageUrl(images[i]))) {
for (let j = 0; j < repeats; j++) {
objImageSet.add(i)
}
}
}
indexes = objIndexes
if (curIndex && curIndex !== newImage) {
setImageTo(curIndex)
}
saveIndexes()
} catch {}
}
newImage = getIndex(false)
curImage = newImage
loadIndexes()
loadTime()
fadeImageTo(newImage)
saveIndexes()
saveTime()
let lastTime = Date.now()
setInterval(() => {
// Handle regaining focus
// This is janky
const activeTime = loadActiveTime()
const now = Date.now()
if (shouldRun && activeTime > lastTime) {
// Became active
const timeElapsed = Math.max(0, activeTime - loadInactiveTime())
if (timeElapsed < 100) {
// Not inactive
loadTime()
} else {
// Went inactive
fadeTime += timeElapsed
}
}
if (shouldRun) {
const now = Date.now()
if (now - fadeTime >= fadeInterval + fadeDuration) {
fadeImageTo(getIndex())
saveIndexes()
}
}
// wasRunning = shouldRun
lastTime = Date.now()
}, 1000)
// ##############################################################
// ##################### VISIBILITY AND FOCUS #####################
let focusChangeTime = Date.now()
async function onFocus() {
focusChangeTime = Date.now()
saveActiveTime()
loadIndexes()
frontDiv.style.animationPlayState = 'running';
backDiv.style.animationPlayState = 'running';
}
function onLostFocus() {
focusChangeTime = Date.now()
saveInactiveTime()
frontDiv.style.animationPlayState = 'paused';
backDiv.style.animationPlayState = 'paused';
}
// Tab closing
window.addEventListener('beforeunload', function(event) {
shouldRun = false
onLostFocus()
})
// Event listener for when the document gains focus
window.addEventListener('focus', function(event) {
docFocused = true
if (!shouldRun && docVisible) {
shouldRun = true
onFocus()
}
});
// Event listener for when the document loses focus
window.addEventListener('blur', function(event) {
docFocused = false
if (shouldRun) {
shouldRun = false
onLostFocus()
}
});
document.addEventListener('visibilitychange', function() {
docVisible = document.visibilityState === 'visible'
const focused = docVisible && docFocused
if (shouldRun !== focused) {
shouldRun = focused
if (shouldRun) onFocus()
else onLostFocus()
}
});
// ################################################################
// ##################### UI MANIPULATION #####################
const findInterval = 200
const findTimeout = 4000
const findMaxIterations = findTimeout / findInterval
const findQueue = []
function findIteration(findObj) {
return new Promise(resolve => {
if (findObj.url !== window.location.href) {
resolve(true)
return
}
let el;
try { el = findObj.selector() } catch {}
if (el) {
findObj.callback(el)
setTimeout(() => {
el = undefined
try { el = findObj.selector() } catch {}
if (el) {
findObj.callback(el)
resolve(true)
} else resolve(++findObj.iteration >= findMaxIterations)
});
} else resolve(++findObj.iteration >= findMaxIterations)
})
}
async function findTimer() {
while (findQueue.length !== 0) {
const promises = []
for (const findObj of findQueue) {
promises.push(findIteration(findObj))
}
for (let i = promises.length - 1; i >= 0; i--) {
if (await promises[i]) {
// Remove find object from queue
findQueue.splice(i, 1)
}
}
if (findQueue.length !== 0) {
await new Promise(res => setTimeout(res, findInterval))
}
}
}
let findIntervalId = 0
async function find(selector, callback) {
const obj = {
selector: selector,
callback: callback,
iteration: 0,
url: window.location.href
}
if (await findIteration(obj)) return
findQueue.push(obj)
if (findQueue.length === 1) findTimer();
}
function hideSideBarItems() {
function hide(el) {
el.style.display = 'none'
}
// Can't remove these or the page breaks
find(() => document.querySelector('[aria-label="Lists"]'), hide)
find(() => document.querySelector('[aria-label="Jobs"]'), hide)
find(() => document.querySelector('[aria-label="Verified Orgs"]'), hide)
// find(() => document.querySelector('[aria-label="Communities"]'), hide)
find(() => document.querySelector('[aria-label="Grok"]'), hide)
find(() => document.querySelector('[aria-label="Premium"]'), hide)
find(() => document.querySelector('[aria-label="Business"]'), hide)
}
function findAll() {
const color = 'rgba(0,0,0,0.5)';
function makeInvisible(el) {
el.style.backgroundColor = 'rgba(0,0,0,0)'
}
hideSideBarItems()
find(() => document.querySelector('[aria-label="Search and explore"]'), el => el.remove())
find(() => document.querySelector('[aria-label="Footer"]').parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Offer extended!"]').parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Timeline: Trending now"]').parentElement.parentElement.parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Relevant people"]').parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Who to follow"]').parentElement.parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Subscribe to Premium"]').parentElement.parentElement, el => el.remove())
find(() => document.querySelector('[aria-label="Search"]').parentElement.parentElement.parentElement.parentElement, makeInvisible)
find(() => document.querySelector('[aria-label="Search"]').children[0].children[0], el => el.style.backgroundColor = color)
find(() => document.querySelector('[aria-label="Timeline: Trending now"]').parentElement.parentElement.parentElement, el => el.style.backgroundColor = color)
find(() => document.querySelector('[data-testid="primaryColumn"]'), el => el.style.backgroundColor = color)
find(() => document.querySelector('[data-at-shortcutkeys]'), makeInvisible)
find(() => document.querySelector('[role="progressbar"]').parentElement.children[1], makeInvisible)
find(() => document.querySelector('[aria-label="Home timelkmeline"]').children[0].children[0].children[0].children[0].children[0].children[0], makeInvisible)
find(() => document.querySelector('[role="progressbar"]').parentElement.parentElement, makeInvisible)
setTimeout(() => find(() => document.querySelector('[role="progressbar"]').parentElement.parentElement, makeInvisible), 250)
find(() => document.querySelector('[role="progressbar"]').parentElement.children[1].children[0].children[0].children[0].children[0].children[1].children[1], makeInvisible)
find(() => document.querySelector('[role="progressbar"]').parentElement.children[1].children[0].children[0].children[0].children[0].children[1].children[1].children[1].children[0].children[0], makeInvisible)
find(() => document.querySelector('[role="progressbar"]').parentElement.children[1].children[0].children[0].children[0].children[0].children[1].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0].children[0], makeInvisible)
find(() => {
const arr = document.getElementsByTagName('span')
for (const el of arr) {
if (el.innerHTML === 'Most relevant') return el.parentElement.parentElement
}
}, makeInvisible)
let found = false
for (let i = 0; i <= 1000; i += 100) {
setTimeout(() => {
if (!found) {
const el = document.querySelector('[role="progressbar"]')?.parentElement?.parentElement
if (el) {
makeInvisible(el);
found = true;
} else if (i === 1000) {
find(() => document.querySelector('[role="progressbar"]').parentElement.parentElement, makeInvisible)
}
}
}, i)
}
}
window.addEventListener('resize', hideSideBarItems)
findAll()
let url = '';
setInterval(() => {
// Can't find an event that works
// popstate, load, navigate don't work
let curUrl = window.location.href
if (curUrl !== url) {
url = curUrl
findAll()
}
}, 500)
// Remove the thing that shows when people have tweeted
setInterval(() => document.querySelector('[aria-label="New posts are available. Push the period key to go to the them."]')?.parentElement?.parentElement?.parentElement?.remove(), 1000)
setInterval(() => {
const el = document.querySelector('[data-testid="toolBar"]')
if (el) el.style.backgroundColor = 'rgba(0,0,0,0)'
}, 500)
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment