您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Suite d'accessibilité complète avec loupe et synthèse vocale améliorée
当前为
// ==UserScript== // @name AccesSight // @namespace http://tampermonkey.net/ // @version 3.1 // @description Suite d'accessibilité complète avec loupe et synthèse vocale améliorée // @author Yglsan // @include * // @grant GM_getValue // @grant GM_setValue // @grant GM_addStyle // @license GPL-3.0 // ==/UserScript== (function() { 'use strict'; // Configuration par défaut const configurationParDefaut = { tailleTexte: 16, modeContraste: 'normal', vitesseLecture: 1, surbrillanceLiens: true, modeSombre: false, loupe: { activee: false, zoom: 2, suivreCurseur: true, tailleLoupe: 200 }, syntheseVocale: { lireLiens: true, ignorerContenuCache: true, navigationStructurelle: true } }; // État courant let configurationActuelle = { ...configurationParDefaut, ...GM_getValue('configurationAccesSight', {}) }; // Création de l'interface utilisateur principale function creerPanneauControle() { const panneau = document.createElement('div'); panneau.id = 'panneau-accesight'; panneau.style.cssText = ` position: fixed; top: ${configurationActuelle.positionSauvegardee.y}px; left: ${configurationActuelle.positionSauvegardee.x}px; background: ${configurationActuelle.modeSombre ? '#333' : '#FFF'}; color: ${configurationActuelle.modeSombre ? '#FFF' : '#000'}; padding: 1rem; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 10000; font-family: Arial, sans-serif; min-width: 300px; cursor: move; user-select: none; `; // En-tête du panneau const enTete = document.createElement('div'); enTete.innerHTML = ` <h2 style="margin: 0 0 1rem 0; font-size: 1.2rem;">AccesSight Pro+</h2> <button aria-label="Fermer le panneau" style="position: absolute; top: 5px; right: 5px; background: none; border: none; cursor: pointer; color: inherit;"> × </button> `; panneau.appendChild(enTete); // Contrôles principaux const controles = [ { type: 'curseur', libelle: 'Taille du texte', min: 12, max: 36, valeur: configurationActuelle.tailleTexte, propriete: 'tailleTexte', pas: 1 }, { type: 'selection', libelle: 'Mode de contraste', options: ['Normal', 'Élevé', 'Inversé'], valeur: configurationActuelle.modeContraste, propriete: 'modeContraste' }, { type: 'bouton', libelle: 'Lire le contenu', action: 'demarrerLectureVocale' }, { type: 'interrupteur', libelle: 'Mode sombre', valeur: configurationActuelle.modeSombre, propriete: 'modeSombre' }, { type: 'interrupteur', libelle: 'Activer la loupe', valeur: configurationActuelle.loupe.activee, propriete: 'loupe.activee' } ]; controles.forEach(controle => { const groupeControle = document.createElement('div'); groupeControle.style.marginBottom = '1rem'; switch(controle.type) { case 'curseur': groupeControle.innerHTML = ` <label style="display: block; margin-bottom: 0.5rem;"> ${controle.libelle}: <output style="display: inline-block; width: 3em;">${controle.valeur}px</output> </label> <input type="range" min="${controle.min}" max="${controle.max}" step="${controle.pas}" value="${controle.valeur}" style="width: 100%;" data-propriete="${controle.propriete}"> `; break; case 'selection': groupeControle.innerHTML = ` <label style="display: block; margin-bottom: 0.5rem;">${controle.libelle}</label> <select style="width: 100%; padding: 0.25rem;" data-propriete="${controle.propriete}"> ${controle.options.map(option => ` <option value="${option.toLowerCase()}" ${option.toLowerCase() === controle.valeur ? 'selected' : ''}> ${option} </option> `).join('')} </select> `; break; case 'interrupteur': groupeControle.innerHTML = ` <label style="display: flex; align-items: center; gap: 0.75rem;"> <input type="checkbox" ${controle.valeur ? 'checked' : ''} data-propriete="${controle.propriete}" style="margin: 0;"> ${controle.libelle} </label> `; break; case 'bouton': groupeControle.innerHTML = ` <button style="width: 100%; padding: 0.75rem; background: #007bff; color: white; border: none; border-radius: 4px;" data-action="${controle.action}"> ${controle.libelle} </button> `; break; } panneau.appendChild(groupeControle); }); // Gestion des événements panneau.querySelectorAll('input, select').forEach(element => { element.addEventListener('input', gererChangementConfiguration); element.addEventListener('mousedown', arreterPropagationEvenement); element.addEventListener('touchstart', arreterPropagationEvenement); }); panneau.querySelector('[data-action="demarrerLectureVocale"]').addEventListener('click', basculerLectureVocale); // Gestion du déplacement du panneau let deplacementActif = false; let positionInitialeX = 0; let positionInitialeY = 0; panneau.addEventListener('mousedown', commencerDeplacement); document.addEventListener('mousemove', deplacerPanneau); document.addEventListener('mouseup', arreterDeplacement); function commencerDeplacement(evenement) { if (evenement.target.tagName === 'INPUT' || evenement.target.tagName === 'SELECT') return; deplacementActif = true; positionInitialeX = evenement.clientX - panneau.offsetLeft; positionInitialeY = evenement.clientY - panneau.offsetTop; } function deplacerPanneau(evenement) { if (deplacementActif) { evenement.preventDefault(); panneau.style.left = `${evenement.clientX - positionInitialeX}px`; panneau.style.top = `${evenement.clientY - positionInitialeY}px`; } } function arreterDeplacement() { deplacementActif = false; configurationActuelle.positionSauvegardee = { x: parseInt(panneau.style.left), y: parseInt(panneau.style.top) }; GM_setValue('configurationAccesSight', configurationActuelle); } document.body.appendChild(panneau); appliquerParametresAccessibilite(); } // Application des paramètres d'accessibilité function appliquerParametresAccessibilite() { // Taille du texte document.documentElement.style.fontSize = `${configurationActuelle.tailleTexte}px`; // Contraste GM_addStyle(` body { filter: ${configurationActuelle.modeContraste === 'élevé' ? 'contrast(150%)' : configurationActuelle.modeContraste === 'inversé' ? 'invert(1) hue-rotate(180deg)' : 'none'}; } `); // Mode sombre if (configurationActuelle.modeSombre) { GM_addStyle(` body { background-color: #1a1a1a !important; color: #ffffff !important; } `); } // Surbrillance des liens if (configurationActuelle.surbrillanceLiens) { GM_addStyle(` a { outline: 2px solid #ff0000 !important; padding: 2px !important; } `); } // Gestion de la loupe if (configurationActuelle.loupe.activee && !document.getElementById('loupe-accesight')) { creerLoupe(); } } // Création de la loupe function creerLoupe() { const loupe = document.createElement('div'); loupe.id = 'loupe-accesight'; loupe.style.cssText = ` position: absolute; width: ${configurationActuelle.loupe.tailleLoupe}px; height: ${configurationActuelle.loupe.tailleLoupe}px; border: 2px solid #ff0000; border-radius: 50%; overflow: hidden; pointer-events: none; display: none; z-index: 100000; background: white; box-shadow: 0 0 20px rgba(0,0,0,0.3); `; const contenuLoupe = document.createElement('div'); contenuLoupe.style.cssText = ` transform-origin: 0 0; will-change: transform; width: ${document.documentElement.offsetWidth}px; height: ${document.documentElement.offsetHeight}px; `; loupe.appendChild(contenuLoupe); document.body.appendChild(loupe); document.addEventListener('mousemove', evenement => { if (!configurationActuelle.loupe.activee) return; const positionX = evenement.clientX; const positionY = evenement.clientY; loupe.style.display = 'block'; loupe.style.left = `${positionX + 20}px`; loupe.style.top = `${positionY + 20}px`; const zoom = configurationActuelle.loupe.zoom; contenuLoupe.style.transform = ` translate(${-positionX * zoom + configurationActuelle.loupe.tailleLoupe/2}px, ${-positionY * zoom + configurationActuelle.loupe.tailleLoupe/2}px) scale(${zoom}) `; contenuLoupe.innerHTML = document.documentElement.cloneNode(true); }); } // Gestion de la synthèse vocale let instanceLectureVocale = null; function basculerLectureVocale() { if (!instanceLectureVocale) { demarrerLectureVocale(); } else { arreterLectureVocale(); } } function demarrerLectureVocale() { const contenuStructure = extraireContenuStructure(); instanceLectureVocale = new SpeechSynthesisUtterance(contenuStructure); instanceLectureVocale.rate = configurationActuelle.vitesseLecture; instanceLectureVocale.onboundary = evenement => surlignerMotCourant(evenement); instanceLectureVocale.onend = () => reinitialiserSurlignage(); window.speechSynthesis.speak(instanceLectureVocale); } function extraireContenuStructure() { const elementsVisibles = Array.from(document.body.querySelectorAll( 'h1, h2, h3, h4, h5, h6, p, li, caption, figcaption, blockquote' )).filter(element => { return configurationActuelle.syntheseVocale.ignorerContenuCache ? element.offsetParent !== null : true; }); return elementsVisibles.map(element => { const typeElement = element.tagName.toLowerCase(); return `${typeElement} : ${element.textContent}`; }).join('. '); } function surlignerMotCourant(evenement) { const indexCaractere = evenement.charIndex; const texteComplet = instanceLectureVocale.text; const mots = texteComplet.split(/\s+/); let indexAccumule = 0; let motCourant = ''; for (const mot of mots) { indexAccumule += mot.length + 1; if (indexAccumule > indexCaractere) { motCourant = mot; break; } } const elements = document.querySelectorAll('*:not(script):not(style)'); elements.forEach(element => { element.childNodes.forEach(node => { if (node.nodeType === Node.TEXT_NODE && node.textContent.includes(motCourant)) { const span = document.createElement('span'); span.style.backgroundColor = 'yellow'; span.style.color = 'black'; span.textContent = motCourant; const nouveauContenu = node.textContent.replace( new RegExp(motCourant, 'g'), span.outerHTML ); const nouveauNoeud = document.createRange().createContextualFragment(nouveauContenu); node.replaceWith(...nouveauNoeud.childNodes); } }); }); } function reinitialiserSurlignage() { document.querySelectorAll('span[style*="yellow"]').forEach(span => { const parent = span.parentNode; parent.replaceChild(document.createTextNode(span.textContent), span); }); instanceLectureVocale = null; } // Gestion des changements de configuration function gererChangementConfiguration(evenement) { const propriete = evenement.target.dataset.propriete; let valeur = evenement.target.type === 'checkbox' ? evenement.target.checked : evenement.target.value; if (propriete === 'tailleTexte') valeur = parseInt(valeur); if (propriete.includes('.')) { const [parent, enfant] = propriete.split('.'); configurationActuelle[parent][enfant] = valeur; } else { configurationActuelle[propriete] = valeur; } GM_setValue('configurationAccesSight', configurationActuelle); appliquerParametresAccessibilite(); if (propriete === 'tailleTexte') { evenement.target.parentNode.querySelector('output').textContent = `${valeur}px`; } } // Fonctions utilitaires function arreterPropagationEvenement(evenement) { evenement.stopPropagation(); } // Initialisation function initialiser() { if (!document.getElementById('panneau-accesight')) { creerPanneauControle(); } } // Démarrage if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initialiser); } else { initialiser(); } })();