您需要先安装一个扩展,例如 篡改猴、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();
- }
- })();