NOTICE: By continued use of this site you understand and agree to the binding Terms of Service and Privacy Policy.
/** * @file * Stylizes broken images with unobtrusive placeholder. * * This file can be used as a Tampermonkey userscript or it can be included in * the source of any website. */ // ==UserScript== // @name Broken Image Stylizer // @version 0.0.4 // @license MIT // @description Stylizes broken images with unobtrusive placeholder. // @author Mike Shiyan // @namespace Mike Shiyan // @source https://github.com/mikeshiyan/broken_image_stylizer // @supportURL https://github.com/mikeshiyan/broken_image_stylizer/issues // ==/UserScript== (function () { 'use strict'; /** * Stylizes the image. * * @param image * Image element. */ function stylize (image) { var width = image.width; var height = image.height; var text = width + ' × ' + height; // Calculate the font size. The final value cannot be less than 5px or more // than a 3-quarters of image height. var fontSize = Math.max(Math.min(width / text.length * 1.5, height * 0.75), 5); image.alt = text; // Trick the browser to show the just added 'alt' attribute. image.src = null; image.removeAttribute('src'); // Apply styles. image.style.backgroundImage = 'url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAACCAYAAABytg0kAAAAE0lEQVR42mNgAIIzZ878hxBAAAA8bwbHJ1VugQAAAABJRU5ErkJggg==")'; image.style.fontFamily = 'monospace'; image.style.fontSize = fontSize + 'px'; image.style.lineHeight = (height - 4) + 'px'; image.style.textAlign = 'center'; image.classList.add('broken-image'); } /** * Iterates over images in document. * * @param handler * A callback to run on each image. Accepts 2 arguments: image element and * index number of element in array. * @param filter * (optional) A callback to filter images array. Accepts 1 argument: an * image element. Must return TRUE indicating the image should be in * resulting array, or FALSE to skip it. */ function iterateImages (handler, filter) { var images = document.getElementsByTagName('img'); if (typeof filter != 'undefined') { images = Array.prototype.filter.call(images, filter); } Array.prototype.forEach.call(images, handler); } /** * Gets the list of broken images from the storage. * * @return * Object which keys are image source URLs. */ function storageGet () { var data; if (typeof localStorage != 'undefined') { data = JSON.parse(localStorage.getItem('broken_images')); } if (!data) { data = {}; } return data; } /** * Stores a list of broken images. * * @param data * Object to store. */ function storageSet (data) { if (typeof localStorage != 'undefined') { localStorage.setItem('broken_images', JSON.stringify(data)); } } var cache = {}; /** * Executes the code after HTML of the page is fully loaded. */ var onDocumentReady = function () { // Set onError handler for all images. iterateImages(function (image) { image.onerror = function () { // Check that image is not processed yet. if (!this.classList.contains('broken-image')) { cache[this.src] = 1; stylize(this); } this.onerror = null; }; }); // Change image sources without waiting for images attempting to load. var data = storageGet(); iterateImages(stylize, function (image) { // Check that image element has the source and that this source is // already known as broken. Use getAttribute() method to get initial // property value because the direct object property (image.src) always // contains string - the full path to source or an empty "". return image.getAttribute('src') !== null && !!data[image.src]; }); }; if (document.readyState != 'loading') { onDocumentReady(); } else { document.addEventListener('DOMContentLoaded', onDocumentReady); } window.addEventListener('load', function () { var data = storageGet(); Object.assign(data, cache); storageSet(data); }); window.addEventListener('resize', function () { iterateImages(stylize, function (image) { return image.classList.contains('broken-image'); }); }); })();