ホームページ >ウェブフロントエンド >CSSチュートリアル >ブラウザ拡張機能を作成する方法
ブラウザ拡張機能を使用している場合があります。広告ブロッカー、パスワードマネージャー、PDF視聴者など、一部の拡張機能は非常に人気があり便利です。これらの拡張機能(または「アドオン」)はこれに限定されていません - あなたはそれらでもっと多くのことをすることができます!この記事では、拡張機能を作成する方法について説明します。最終的には、複数のブラウザで実行します。
特定のコメントをコメントセクションの上部に移動し、画面読者にaria属性を追加することにより、Redditアクセシビリティを改善する「Reddit Transcription」と呼ばれる拡張機能を作成します。また、オプションを追加して、より良いテキストコントラストのためにコメントに境界と背景を追加することにより、さらに一歩進めます。
全体のアイデアは、ブラウザ拡張機能を開発する方法について良いアイデアを提供することです。最初に、Google Chrome、Microsoft Edge、BraveなどのChromiumベースのブラウザの拡張機能を作成します。将来の記事では、拡張機能をポートしてFirefoxと互換性があり、最近、ブラウザのMacOSおよびiOSバージョンのWeb拡張サポートにSafariを追加しました。
githubコードベースは準備ができていますか?段階的にやりましょう。
まず、プロジェクトワークスペースが必要です。本当に必要なのは、フォルダーを作成して名前を付けることだけです(Transistors-of-Redditと名付けました)。次に、ソースコードのSRCと呼ばれるフォルダーを作成します。
エントリポイントは、拡張機能に関する一般情報(つまり、拡張名、説明など)を含み、実行する権限またはスクリプトを定義するファイルです。
エントリポイントは、作成したばかりのSRCフォルダーにあるManifest.jsonファイルです。その中に、次の3つのプロパティを追加しましょう。
<code>{ "manifest_version": 3, "name": "Reddit 转录器", "version": "1.0" }</code>
manifest_versionは、npmまたはノードのバージョンに似ています。どのAPIが利用可能か(または利用できない)を定義します。最先端の作業には、最新バージョン3(MV3とも呼ばれます)を使用します。
2番目のプロパティは名前で、拡張子名を指定します。この名前は、Chrome Web StoreやChrome:// Extensionsページなど、どこにでも表示されるときに拡張機能が表示される名前です。
次に、バージョンがあります。拡張機能をバージョン番号でマークします。このプロパティ(manifest_versionとは対照的に)は、数字とドットのみを含む文字列であることを忘れないでください(例:1.3.5)。
実際、拡張機能のコンテキストを追加するためにさらに追加できます。たとえば、拡張機能の機能を説明する説明を提供できます。ユーザーが拡張機能を使用するときに遭遇するものをよりよく理解することができるため、この情報を提供することが最善です。
この例では、説明を追加するだけでなく、Chrome Webストアがその拡張機能ページに指すアイコンとURLも提供します。
<code>{ "description": "使Reddit 对残疾用户更易于访问。", "icons": { "16": "images/logo/16.png", "48": "images/logo/48.png", "128": "images/logo/128.png" }, "homepage_url": "https://www.php.cn/link/530ebdeb0491c0459e00298fcdb3a2bd/extensions/tor/" }</code>
拡張機能の大きな利点の1つは、APIがブラウザと直接対話できることです。ただし、Manifest.jsonファイルにも含まれている拡張機能にこれらのアクセス許可を明示的に付与する必要があります。
<code>{ "manifest_version": 3, "name": "Reddit 转录器", "version": "1.0", "description": "使Reddit 对残疾用户更易于访问。", "icons": { "16": "images/logo/16.png", "48": "images/logo/48.png", "128": "images/logo/128.png" }, "homepage_url": "https://www.php.cn/link/530ebdeb0491c0459e00298fcdb3a2bd/extensions/tor/", "permissions": [ "storage", "webNavigation" ] }</code>
この拡張機能を付与したばかりの権限は何ですか?まず、ストレージ。この拡張機能がユーザーの設定を保存する必要があるため、ブラウザのWebストレージにアクセスして保存する必要があります。たとえば、ユーザーがコメントに赤い境界線を表示することを望んでいる場合、再度設定する代わりに、次回の設定を保存します。
また、ユーザーが現在の画面にどのように移動するかを確認するための拡張許可を付与します。 Redditは単一ページアプリケーション(SPA)です。つまり、ページの更新をトリガーしません。 Redditは、クリックするときに投稿にコメントをロードするため、この相互作用を「キャプチャ」する必要があります。そのため、WebNavigationを利用しています。
Manifest.jsonに新しいエントリを追加する必要があるため、ページ上のコードの実行を後で説明します。
/説明許可許可に応じて、ブラウザはユーザーに許可を受け入れるように警告を表示する場合があります。ただし、特定の許可のみがこのようなものであり、Chromeにはこれらのアクセス許可の概要があります。
ブラウザ拡張機能には、組み込みの国際化(I18N)APIがあります。これにより、複数の言語で翻訳を管理できます(フルリスト)。 APIを使用するには、manifest.jsonファイルで翻訳言語とデフォルト言語を定義する必要があります。
<code>"default_locale": "en"</code>
これは英語を言語として設定します。ブラウザがサポートされていない他の言語に設定されている場合、拡張機能はデフォルトのロケールに戻ります(この例のEN)。
翻訳は_localesディレクトリで定義されています。各言語がサポートするフォルダーを作成しましょう。各サブディレクトリには、独自のmessages.jsonファイルがあります。
<code>src └─ _locales └─ en └─ messages.json └─ fr └─ messages.json</code>
翻訳ファイルには複数の部品が含まれています。
これがすべてをまとめる例です。
<code>{ "userGreeting": { // 翻译键(“id”) "message": "Good $daytime$, $user$!" // 翻译"description": "用户问候", // 翻译人员的可选描述"placeholders": { // 可选占位符"daytime": { // 如消息中所引用"content": "$1", "example": "morning" // 我们内容的示例值}, "user": { "content": "$1", "example": "Lars" } } } }</code>
プレースホルダーの使用は少し難しいです。まず、メッセージ内のプレースホルダーを定義する必要があります。プレースホルダーは$文字に囲まれる必要があります。その後、プレースホルダーを「プレースホルダーリスト」に追加する必要があります。これは少し直感的ではありませんが、Chromeはプレースホルダーにどのような価値を挿入すべきかを知りたいと思っています。ここでは(明らかに)動的値を使用したいので、挿入した値を参照するために特別なコンテンツ値1ドルを使用します。
例のプロパティはオプションです。プレースホルダーが何であるかを翻訳者に促すために使用できます(ただし、実際には表示されません)。
拡張機能のために次の翻訳を定義する必要があります。それらをコピーしてmessages.jsonファイルに貼り付けます。より多くの言語を追加してください(たとえば、ドイツ語を話す場合は、_localesにdeフォルダーを追加します。
<code>{ "name": { "message": "Reddit 转录器" }, "description": { "message": "子reddits 的辅助图像描述。" }, "popupManageSettings": { "message": "管理设置" }, "optionsPageTitle": { "message": "设置" }, "sectionGeneral": { "message": "常规设置" }, "settingBorder": { "message": "显示评论边框" }, "settingBackground": { "message": "显示评论背景" } }</code>
なぜ私たちがi18n許可なしで登録したのか疑問に思うかもしれませんよね?クロムは、すべての許可を登録する必要がないため、この点で少し奇妙です。一部(Chrome.i18nなど)は、マニフェストでの登録を必要としません。その他のアクセス許可はエントリを必要としますが、拡張子がインストールされているときにユーザーには表示されません。他のいくつかの許可は「混合」(Chrome.runtimeなど)です。これは、それらの機能の一部を宣言することなく使用できることを意味しますが、同じAPIの他の機能にはマニフェストに登録されたエントリが必要です。これらの違いを完全に理解するには、ドキュメントを見る必要があります。
エンドユーザーが最初に見ることは、Chrome Webストアのエントリまたは拡張機能の概要ページです。マニフェストファイルを調整して、すべてが翻訳されていることを確認する必要があります。
<code>{ // 更新这些条目"name": "__MSG_name__", "description": "__MSG_description__" }</code>
この構文を適用すると、messages.jsonファイルで対応する変換が使用されます(たとえば、_msg名は名前を使用して翻訳されます)。
HTMLファイルに翻訳を適用するには、JavaScriptが必要です。
<code>chrome.i18n.getMessage('name');</code>
このコードは、定義した翻訳(つまり、Reddit転写産物)を返します。プレースホルダーも同様の方法で行うことができます。
<code>chrome.i18n.getMessage('userGreeting', { daytime: 'morning', user: 'Lars' });</code>
この方法ですべての要素に翻訳を適用するのは面倒です。ただし、データ属性に従って翻訳を実行する小さなスクリプトを書くことができます。 SRCディレクトリに新しいJSフォルダーを作成してから、新しいutil.jsファイルを追加しましょう。
<code>src └─ js └─ util.js</code>
これはタスクを達成できます:
<code>const i18n = document.querySelectorAll("[data-intl]"); i18n.forEach(msg => { msg.innerHTML = chrome.i18n.getMessage(msg.dataset.intl); }); chrome.i18n.getAcceptLanguages(languages => { document.documentElement.lang = languages[0]; });</code>
このスクリプトをHTMLページに追加した後、要素にData-inTL属性を追加して内容を設定できます。ドキュメント言語は、ユーザーの言語に応じて設定されます。
<code> </code>
<code>管理设置</code>
実際のプログラミングに飛び込む前に、2ページを作成する必要があります。
ページを作成するために必要なフォルダーとファイルの概要を次に示します。
<code>src ├─ css | └─ paintBucket.css ├─ popup | ├─ popup.html | ├─ popup.css | └─ popup.js └─ options ├─ options.html ├─ options.css └─ options.js</code>
.CSSファイルには純粋なCSSが含まれています。あなたの読者のほとんどがすでにCSSの仕組みを完全に理解していることを知っているので、私は詳細には触れません。プロジェクトのGitHubリポジトリからスタイルをコピーして貼り付けることができます。
ポップアップはタブではなく、そのサイズは内部の内容に依存することに注意してください。固定サイズのポップアップを使用する場合は、HTML要素に幅と高さのプロパティを設定できます。
これは、CSSとJavaScriptファイルをリンクするHTMLスケルトンです。
タイトルとボタンを追加します。 `` `
<code><meta charset="UTF-8"> <meta content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" name="viewport"> <meta content="ie=edge" http-equiv="X-UA-Compatible"></code> <title data-intl="name"></title><link href="../css/paintBucket.css" rel="stylesheet"><link href="popup.css" rel="stylesheet"><h1></h1>
<code>h1 包含扩展程序名称和版本;按钮用于打开选项页面。标题将不会填充翻译(因为它缺少data-intl 属性),并且按钮还没有任何点击处理程序,因此我们需要填充popup.js 文件:</code>
const title = document.getElementById( 'title');
title.textContent = ${manifest.name} (${manifest.version})
;
settingsbtn.addeventlistener( 'click'、()=> {chrome.runtime.openoptionspage();});
<code>此脚本首先查找manifest 文件。Chrome 提供了包含getManifest 方法的runtime API(此特定方法不需要runtime 权限)。它将我们的manifest.json 返回为JSON 对象。在我们使用扩展程序名称和版本填充标题后,我们可以向设置按钮添加事件侦听器。如果用户与之交互,我们将使用chrome.runtime.openOptionsPage() 打开选项页面(同样不需要权限条目)。弹出窗口页面现在已完成,但扩展程序尚不知道它的存在。我们必须通过将以下属性附加到manifest.json 文件来注册弹出窗口。</code>
"Action":{"default_popup": "popup/popup.html"、 "default_icon":{"16": "images/logo/16.png"、 "48": "images/logo/48.png"、 "128": "images/logo/128.png}}、
<code>#### 创建选项页面创建此页面的过程与我们刚刚完成的过程非常相似。首先,我们填充options.html 文件。以下是一些我们可以使用的标记:</code>
<circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path>
<code><div></div></code> <p><a href="https://www.php.cn/link/530ebdeb0491c0459e00298fcdb3a2bd" target="_blank">lars.koelker.dev</a>によって提供されるReddit転写筋拡張。</p>
Redditは、Reddit、Inc。の登録商標です。この拡張機能は、いかなる方法でもReddit、Inc。に承認されていないか、関連していません。
<code>目前还没有实际的选项(只有它们的包装器)。我们需要编写选项页面的脚本。首先,我们在options.js 中定义变量以访问我们的包装器和默认设置。“冻结”我们的默认设置可以防止我们以后意外修改它们。</code>
const defaultsettings = object.freeze({border:false、false、});
<code>接下来,我们需要加载保存的设置。我们可以为此使用(先前注册的)存储API。具体来说,我们需要定义是要在本地存储数据(chrome.storage.local) 还是通过登录的所有设备同步设置(chrome.storage.sync)。让我们在这个项目中使用本地存储。需要使用get 方法检索值。它接受两个参数: 1. 我们要加载的条目2. 包含值的回调我们的条目可以是字符串(例如,下面的settings)或条目数组(如果我们想要加载多个条目,则很有用)。回调函数中的参数包含我们先前在{ settings: ... } 中定义的所有条目的对象:</code>
Chrome.storage.local.get( 'settings'、({settings})=> {const options = settings ?? defaultsettings; //設定が定義されていない場合、(!settings){chrome.storage.local.set({settings:default:default:defationsetings、};};};};
//オプションを作成および表示const generaloptions = object.keys(options).filter(x =>!x.startswith( 'advanced'));
generaloptions.foreach(option => createoption(option、option、generalection));
<code>为了呈现选项,我们还需要创建一个createOption() 函数。</code>
function createoption(setting object、wrapper){const settingwrapper = <div><label for="${setting}">${chrome.i18n.getMessage(</label></div>
"div");
`;
const toggleswitch = settingwrapper.queryselector( "label.is-switch");
input.OnChange =()=> {golleswitch.setTribute( 'aria-checked'、input.checked);
Toggleswitch.onkeydown = e => {if(e.key === "|| e.key === {e.preventdefault();
wrapper.appendChild(SettingWrapper);
<code>在我们的开关(又名单选按钮)的onchange 事件侦听器中,我们调用函数updateSetting。此方法将把单选按钮的更新值写入存储中。为此,我们将使用set 函数。它有两个参数:我们要覆盖的条目和(可选)回调(在本例中我们不使用)。由于我们的settings 条目不是布尔值或字符串,而是一个包含不同设置的对象,因此我们使用扩展运算符(...) 并仅覆盖settings 对象中的实际键(setting)。</code>
関数更新セット(key、value){chrome.storage.local.get( 'settings'、({settings})=> {chrome.storage.local.set({settings:{... settings、
<code> } })</code>
});
<code>同样,我们需要通过将以下条目附加到manifest.json 来“通知”扩展程序我们的选项页面:</code>
"options_ui":{"open_in_tab":true、 "page": "options/options.html"}、
<code>根据您的用例,您还可以通过将open_in_tab 设置为false 来强制选项对话框作为弹出窗口打开。 ### 安装开发扩展程序现在我们已经成功设置了manifest 文件并将弹出窗口和选项页面都添加到了组合中,我们可以安装扩展程序来检查我们的页面是否正常工作。导航到chrome://extensions 并启用“开发者模式”。将出现三个按钮。单击标记为“加载解压”的按钮,然后选择扩展程序的src 文件夹以加载它。扩展程序现在应该已成功安装,并且我们的“Reddit 转录器”图块应该在页面上。我们现在已经可以与扩展程序交互了。单击地址栏旁边的拼图块(?) 图标,然后单击新添加的“Reddit 转录器”扩展程序。您现在应该会看到一个小的弹出窗口,其中包含一个按钮,用于打开选项页面。不错吧?在我的设备上它可能看起来有点不同,因为我在这些屏幕截图中启用了深色模式。如果您启用“显示评论背景”和“显示评论边框”设置,然后重新加载页面,则状态将保留,因为我们将其保存在浏览器的本地存储中。 ### 添加内容脚本好的,我们现在已经可以触发弹出窗口并与扩展程序设置交互,但是扩展程序本身还没有做任何特别有用的事情。为了让它发挥作用,我们将添加一个内容脚本。在js 目录中添加一个名为comment.js 的文件,并确保在manifest.json 文件中定义它:</code>
「content_scripts」:[{"matches":[" ://www.reddit.com/ "]、 "js":["js/comment.js"]}]、
<code>content_scripts 由两部分组成: - matches:此数组保存URL,这些URL 告诉浏览器我们希望内容脚本在何处运行。作为Reddit 的扩展程序,我们希望它在与://www.redit.com/* 匹配的任何页面上运行,其中星号是通配符,用于匹配顶级域之后的任何内容。 - js:此数组包含实际的内容脚本。内容脚本无法与其他(普通)JavaScript 交互。这意味着如果网站的脚本定义了变量或函数,我们就无法访问它。例如:</code>
// script_on_website.js const username = 'lars';
// content_script.js console.log(username);
<code>现在让我们开始编写内容脚本。首先,我们在comment.js 中添加一些常量。这些常量包含稍后将使用的RegEx 表达式和选择器。CommentUtils 用于确定帖子是否包含“tor 评论”,或者是否存在评论包装器。</code>
const messagetypes = object.freeze({comment_page: 'comment_page'、subreddit_page: 'subreddit_page'、main_page: 'main_page'、other_page: 'other_page'、});
const selectors = object.freeze({commentwrapper: 'div [style = " - commentswrapper-grapient-color"]> div、div [style = "max-height:unset"]> div'、torcomment: 'div [data-tor-comment]、' div [data-test-] '});
const urlregex = object.freeze({commentpage:/\/r \/。 \/comments \/。/ 、subredditpage:/\/r\/.*\//});
const commentutils = object.freeze({istorcomment:(comment)=> comment.queryselector( '[data-test-]')?comment.queryselector( '[data-test-]')。 => !! document.querySelector( '[data-reddit-comment-wrapper = "true"]')});
<code>接下来,我们检查用户是否直接打开评论页面(“帖子”),然后执行RegEx 检查并更新directPage 变量。当用户直接打开URL(例如,通过将其键入地址栏或单击另一个页面上的<a> 元素(例如Twitter))时,就会发生这种情况。</a></code>
directPage = false;
<code>除了直接打开页面外,用户通常还会与SPA 交互。为了捕获这种情况,我们可以通过使用runtime API 向comment.js 文件添加消息侦听器。</code>
Chrome.runtime.onmessage.addlistener(msg => {if(msg.type === messageTypes.comment_page){waitforcomment(movecomments);}});
<code>我们现在只需要这些函数。让我们创建一个moveComments() 函数。它将特殊的“tor 评论”移动到评论部分的开头。它还会根据设置中是否启用了边框,有条件地将背景颜色和边框应用于评论。为此,我们调用存储API 并加载settings 条目:</code>
function movecomments(){if(commentutils.commentwrapperexists()){return}
const wrapper = document.queryselector(selectors.commentwrapper)let wrapper.queryselectorall( ${Selectors.commentWrapper} > div
);
wrapper.dataset.redditcommentwrapper = 'true'。
if(directpage){comments = document.queryselectorall( "[data-reddit-comment-wrapper = 'true']> div");
Chrome.storage.local.get( 'settings'、({settings})=> {// highlight 18 comments.foreach(comment.dataset.torcomment = 'true」; settings.border){comment.style.outline = '2px solid red'; comment.style.order = "-1";
<code>applyWaiAria() 函数在moveComments() 函数中调用——它添加aria- 属性。另一个函数创建唯一标识符以与aria- 属性一起使用。</code>
function applywaiaria(postcontent、comment){const postmedia = postcontent.queryselector( 'img [class*= "imagebox-image"]、video');
if(!postmedia){return;
comment.setattribute( 'id'、commentid);
function uuidv4(){return 'xxxxxx-xxxx-4xxx-xxxxxxxxxxxxxx'.Replace(/[xy]/g、function(c){var r = math.random() * 16 | 0、v = c =' x '' '' '' '' '' '' '' '' '' '' '? }
<code>以下函数等待评论加载,如果找到评论包装器,则调用回调参数。</code>
function waitforcomment(callback){const config = {true、subtree:true};
observer.observe(document.documentelement、constive);
function startobservingtimeout(observer、秒){return setimeout(()=> {observer.disconnect();}、1000 *秒);
<code>### 添加服务工作者还记得我们在内容脚本中添加了消息侦听器吗?此侦听器当前未接收消息。我们需要自己将其发送到内容脚本。为此,我们需要注册一个服务工作者。我们必须通过将以下代码附加到manifest.json 来注册服务工作者:</code>
「バックグラウンド」:{"service_worker": "sw.js"}
<code>不要忘记在src 目录中创建sw.js 文件(服务工作者始终需要在扩展程序的根目录src 中创建)。现在,让我们为消息和页面类型创建一些常量:</code>
const messagetypes = object.freeze({comment_page: 'comment_page'、subreddit_page: 'subreddit_page'、main_page: 'main_page'、other_page: 'other_page'、});
const urlregex = object.freeze({commentpage:/\/r \/。 \/comments \/。/ 、subredditpage:/\/r\/.*\//});
constutils = object.freeze({getPageType:(url)=> {if(new url(url).pathname === '/'){return messagetypes.main_page;} ){messageTypes.subreddit_pageを返す}
<code>return messageTypes.OTHER_PAGE;</code>
}});
<code>我们可以添加服务工作者的实际内容。我们使用历史状态上的事件侦听器(onHistoryStateUpdated) 来执行此操作,该侦听器检测何时使用History API 更新页面(通常在SPA 中用于在没有页面刷新情况下导航)。在此侦听器中,我们查询活动选项卡并提取其tabId。然后,我们将包含页面类型和URL 的消息发送到我们的内容脚本。</code>
chrome.webnavigation.onhistorystateUpdated.addlistener(async({url})=> {const [{id:tabid}] = wait chrome.tabs.query({active:true、currentwindow:true});
Chrome.tabs.sendmessage(tabid、{type:utils.getPageType(url)、url});
<code>### 全部完成!我们完成了!导航到Chrome 的扩展程序管理页面(chrome://extensions),然后点击解压扩展程序上的重新加载图标。如果您打开包含“Reddit 转录器”评论和图像转录的Reddit 帖子(例如此帖子),只要我们在扩展程序设置中启用了它,它就会被移动到评论部分的开头并突出显示。 ### 结论这是否像您想象的那么难?在我深入研究之前,它肯定比我想象的要简单得多。在设置manifest.json 并创建任何我们需要的文件和资产后,我们真正做的只是像往常一样编写HTML、CSS 和JavaScript。如果您在途中遇到任何问题,Chrome API 文档是一个很好的资源,可以帮助您重回正轨。再次声明,这是包含我们在本文中介绍的所有代码的GitHub 代码库。阅读它,使用它,并让我知道您的想法!</code>
以上がブラウザ拡張機能を作成する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。