您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Hover tooltip of image displaying alt attribute, original title, some accessibility-related properties, and URL info.
当前为
// ==UserScript== // @name Image Alt to Title // @namespace myfonj // @include * // @grant none // @version 1.6.3 // @run-at document-start // @description Hover tooltip of image displaying alt attribute, original title, some accessibility-related properties, and URL info. // @license CC0 // ==/UserScript== /* * https://gf.qytechs.cn/en/scripts/418348/versions/new * * § Trivia: * ¶ Hover tooltip displays content of nearest element's title attribute (@title). * ¶ Alt attribute (@alt) is possible only at IMG element. * ¶ IMG@alt is not displayed in tooltip. * ¶ IMG cannot have children. * ¶ @title is possible on any element, including IMG. * ¶ IMG@src is also valuable. * * Goal: * Display image alt attribute value in images hover tooltip, add valuable @SRC chunks. * * Details * Pull @alt from image and set it so it is readable as @title tooltip * so that produced title value will not obscure existing parent title * that would be displayed otherwise. Also include image filename from @src, * and additionally path or domain. * * Means * Upon "hover" set image's title attribute. Luckily tooltips delay catches augmented value. * * § Tastcases * * FROM: * <a> * <img> * </a> * TO: * <a> * <img title="Alt missing."> * </a> * * FROM: * <a> * <img alt=""> * </a> * TO: * <a> * <img alt="" title="Alt: ''"> * </a> * * FROM: * <a> * <img alt="░"> * </a> * TO: * <a> * <img alt="░" title="Alt: ░"> * </a> * * FROM: * <a> * <img alt="░" title="▒"> * </a> * TO: * <a> * <img title="Alt: ░, title: ▒"> * </a> * FROM: * <a title="▒"> * <img alt="░"> * </a> * TO: * <a> * <img title="Alt: ░, title: ▒"> * </a> * */ // do not run at image-only pages if (document.querySelector('body>img[alt="' + document.location.href + '"]:only-child')) { // @ts-ignore (GreaseMonkey script is in fact function body) return } const originalTitles = new WeakMap(); let lastSetTitle = ''; const docEl = document.documentElement; const listenerConf = { capture: true, passive: true }; docEl.addEventListener('mouseenter', altToTitle, listenerConf); docEl.addEventListener('mouseleave', restoreTitle, listenerConf); function altToTitle (event) { const tgt = event.target if (tgt.tagName && tgt.tagName == 'IMG') { if(originalTitles.has(tgt) || (tgt.title && tgt.title === lastSetTitle)) { // few times I got situations when mouseout was not triggered // presumably because someting covered the image // or whole context were temporarily replaced or covered // or perhaps it was reconstructed from dirty snapshot // so this should prevent exoponentially growing title return } originalTitles.set(tgt, tgt.getAttribute('title')); altPic(tgt); } } function restoreTitle (event) { const tgt = event.target; if (originalTitles.has(tgt)) { let ot = originalTitles.get(tgt); if(ot === null) { tgt.removeAttribute('title'); } else { tgt.title = ot; } originalTitles.delete(tgt); } } /** * @param {HTMLImageElement} img */ function altPic (img) { try { const separator = '---'; const info = []; const alt = img.getAttribute('alt'); let altText = alt; const title = getClosestTitle(img); const role = img.getAttribute('role'); const isPresentation = role === 'presentation'; if (role) { info.push('Role: ' + role); } switch (alt) { case null: info.push(isPresentation ? `(Alt missing but not needed for this role.)` : `⚠ Alt missing`); break; case '': info.push(`Alt: ""`); break; default: if( alt != alt.trim() ) { // "quote" characters are generally useful only to reveal leading/trailing whitespace altText = `»${alt}«`; } if (alt == title) { info.push(`Alt (=title): ${altText}`); } else { info.push(`Alt: ${altText}`); } } if (title && alt != title) { info.push(separator); info.push('Title: ' + title); } const descby = img.getAttribute('aria-describedby'); if (descby) { info.push(separator); info.push('Described by `' + descby + '`: ' + (document.getElementById(descby) || { textContent: '(element not found)' }).textContent); } // depreated, but let's see // https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/longDesc // https://www.stylemanual.gov.au/format-writing-and-structure/content-formats/images/alt-text-captions-and-titles-images const longdesc = img.getAttribute('longdesc'); if (longdesc) { info.push(separator); info.push('Long Description: ' + longdesc); } const arialabel = img.getAttribute('aria-label'); if (arialabel) { info.push(separator); info.push('Label (ARIA): ' + arialabel); } // https://html5accessibility.com/stuff/2021/02/09/aria-description-by-public-demand-and-to-thunderous-applause/ const histeve = img.getAttribute('aria-description'); if (histeve) { info.push(separator); info.push('Description: ' + histeve); } var fig = getClosestEl(img, 'FIGURE'); if( fig ){ let capt = fig.querySelector('figcaption'); if( capt) { info.push(separator); info.push('Figcaption: ' + capt.textContent.trim()); } } info.push(separator); const srcURI = new URL(img.currentSrc || img.src, img.baseURI); const slugRx = /[^/]+$/; switch (srcURI.protocol) { case 'http:': case 'https:': { if (srcURI.hash) { info.push('Hash: ' + decodeURIComponent(srcURI.hash)); } if (srcURI.search) { info.push('Params: ' + decodeURIComponent(srcURI.search)); } info.push('File: ' + decodeURIComponent(srcURI.pathname.match(slugRx))); let path = srcURI.pathname.replace(slugRx, ''); if(path && path != '/') { info.push('Path: ' + decodeURIComponent(srcURI.pathname.replace(slugRx, ''))); } if (document.location.hostname != srcURI.hostname || window != window.top) { info.push('Host: ' + srcURI.hostname); } break; } case 'data:': { let durichunks = srcURI.href.split(','); info.push(durichunks[0] + ', + ' + durichunks[1].length + ' b.'); break; } default: info.push('Src: ' + srcURI.href); } // ↔ ↕ var CSSsizes = `${img.width} × ${img.height} CSSpx${findRatio(img.width, img.height)}`; var _width_ratio, _height_ratio; if (img.naturalWidth && img.naturalHeight) { // SVG have zero naturals if (img.naturalWidth == img.width && img.naturalHeight == img.height) { CSSsizes += ` (Natural)`; } else { _width_ratio = '~' + (img.width/img.naturalWidth*100).toFixed(0) + '% of '; _height_ratio = '~' + (img.height/img.naturalHeight*100).toFixed(0) + '% of '; if(_height_ratio == _width_ratio) { _height_ratio = ''; } CSSsizes += ` (${_width_ratio}${img.naturalWidth} × ${_height_ratio}${img.naturalHeight} natural px${findRatio(img.naturalWidth,img.naturalHeight)})`; } } info.push('Size: ' + CSSsizes); img.title = info.join('\n'); lastSetTitle = img.title; } catch (e) { // console.error('altPic ERROR', e, img); } } /** * @param {HTMLElement} el */ function getClosestTitle (el) { do { if (el.title) { return el.title; } } while (el = el.parentElement); return '' } /** * @param {HTMLElement} el */ function getClosestEl (el, tagName) { do { if (el.tagName == tagName) { return el; } } while (el = el.parentElement); return false } function findRatio(x,y) { var smallest = Math.min(x,y); var n = 0; var res = n; while (++n <= smallest ) { if( x % n == 0 && y % n == 0) res = n; } if( res == 1 ) { return '' } return ' [' + x/res + ':' + y/res + ']' }
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址