- 投稿日:2021-08-09T15:18:04+09:00
初心者による初心者のためのGatsbyJS覚書3(画像の最適化編)
この記事について 会社の業務でGatsbyJSを少しだけさわった経験がある新卒2年目が作成しています。 参考資料として書籍を使用していますが、筆者がビギナークラスのため読んでいて「?」となる部分や間違っている箇所もあるかと思います。 参考までに、そして間違えている箇所がありましたらご連絡いただけると嬉しいです。 Chapter2:画像の最適化の備忘録メモ 注意 参考資料である「Webサイト高速化のための静的サイトジェネレーター活用入門」では まだGatsby v2の手法が記載されているが、今回環境構築をした際、最新版のv3で作っているので 参考書通りとはならなかったが、付属されているv3用のダウンロード資料をもとに取り組んでみた。 「Gatsby v2」と「Gatsby v3」の違いについて この画像の最適化編で大きく影響してくるのはズバリ 「インストールするプラグインとそのルールの違い」である。 v2までは「gatsby-image」というプラグインをインストールしていたが、 v3からは新しく「gatsby-plugin-image」というプラグインが導入されている。 ざっくり説明するとgatsby-imageで必要だった画像最適化までの手順が gatsby-plugin-imageだと大幅に削減されるよって話らしい。 gatsby-plugin-imageではStaticImageとGatsbyImageの2つが使用でき、 StaticImageはGraphQLを使わずに最適化した画像を引っ張ってこれるため使い勝手が◎ ひとまず必要なプラグインたちをインストールしておく。 npm install gatsby-plugin-image gatsby-plugin-sharp gatsby-transformer-sharp gatsby-source-filesystem gatsby-transformer-sharpは後にダイナミックイメージ(GatsbyImage)を使用する場合に必要 gatsby-source-filesystemはStaticのImageを使っている場合必要 あとは使いたいファイルにインポートして使うのみ。 import { StaticImage, GatsbyImage } from "gatsby-plugin-image" バージョンの違いで解説を使いつつも手間取った部分 gatsby-imageを使用している場合、src/配下のimageを取得しようとするとクエリを作成する必要がある。 なので参考資料の解説も「クエリを使用していること」を前提に進んでいくわけだが、 やはり諸々使い勝手が違いすぎで初心者大混乱。笑 GraphQLの構造も自分が見つけたいデータがなかなか見つからず大冒険の果てにデータを探し当て 達成感とともにハッと気がつく。「あれ、そういえばStaticImage使えば早いのでは、」と。 そして解説のPDFにもすこし進むとそのことが記載されている。。。(早く言ってよ心の中で叫ぶ。) 結局クエリを使用しない方法を取ったので日の目をみることがはなかったが 頭を悩ませた記念にデータ構造だけ写真に収めておくことにする。 悩んだのはFULL WIDTHなどをもつlayoutのチェックボックスがなかなか見つからなかったため。 childImageSharpが同階層に2つあるのに気がつけなかったのが運の尽き、、、 GraphQLでなんか欲しいデータ取れないな、、、と思ったら 「その階層ではないのでは?と広い視野でパトロールをしましょう。」が今回の教訓。 そのlayoutでの選択肢ってどういう意味?となったのでそれについての参考はこちら それをクエリでこんな感じで各画像ごとにエイリアスをつけて取得。 export const query = graphql` query { hero: file(relativePath: { eq: "hero.jpg" }) { childImageSharp { gatsbyImageData(layout: FULL_WIDTH) } } fruit: file(relativePath: { eq: "fruit.jpg" }) { childImageSharp { gatsbyImageData(width: 320, layout: CONSTRAINED) } } } ` 画像を出力する部分にはこのように記述。 <GatsbyImage image={data.hero.childImageSharp.gatsbyImageData} alt="" style={{ height: "100%" }} /> <GatsbyImage image={data.grain.childImageSharp.gatsbyImageData} alt="" /> StaticImageで手っ取り早く画像最適化 StaticImageならクエリいらずでGatsbyImageと同等の画像の最適化ができるらしい。最高。 GatsbyImageを使用したときに指定していたlayoutの設定もこのように書けば指定できる。 StaticImageしか使用しないのであればgraphqlやGatsbyImageのインポートは不要。 //例1 <StaticImage src="../images/hero.jpg" alt="" layout="fullWidth" /> //例2 <StaticImage src="../images/fruit.jpg" alt="" layout="constrained" style={{ width :"320px"}} /> プラグインを使用して最適化した画像のスタイルを整える StaticImageやGatsbyImageを使用して画像を最適化してみると さっきまで整っていた見た目に変な余白、ズレ、見えなくなるなどの事象が発生。 「なんだこれ?」と思っていたがどうやらプラグインで最適化すると今まで効いていたCSSが無効になるとか。 解説によると「画像の読み込み中もスペースを確保するためにgatsby-image-wrapperが画像の縦横比にあわせたボックスを構成してくれっちゃってる」らしい。 解決方法はこんなかんじ。 <StaticImage src="../images/hero.jpg" alt="" layout="fullWidth" style={{ height :"100%"}} //ラッパーに高さ100%を指定 /> cssファイルでgatsby-image-wrapperの親要素にあたるクラスfigureにmax-height:100%;を与える。 高さは子要素のimgに合わせて設定。 メディアサイズごとのサイズ指定も追加しておく。 .hero figure img { width: 100%; height: 450px; object-fit: cover; } //追加 .hero figure { max-height: 100%; height: 450px; } @media (min-width: 768px) { //追加 .hero figure { height: 750px; } .hero figure img { height: 750px; } } 背景画像を最適化する 背景画像として画像が使用されている場合はまず、CSSで背景画像を指定している部分をnoneへ変更。 JSXへ新しく背景画像を表示するためのマークアップを追加。 <footer> <div className="container"> //もとからある要素.... //追加 <div className="back"> <StaticImage src="../images/pattern.jpg" alt="" layout="fullWidth" style={{ height :"100%"}} /> </div> </div> </footer> CSSで大きさを調整して、positionを指定。z-indexで重なりを調整する。 /*フッター背景画像*/ .footer{ position: relative; } .footer .container{ position: relative; z-index: 10; } .footer .back{ position: absolute; top: 0; bottom: 0; left: 0; right: 0; margin: auto; } 画質が荒いとき gatsby-config.jsでqualityの調整をする。 plugins: [ { resolve: `gatsby-plugin-sharp`, options: { defaults: { quality: 90, //デフォルトは50 }, }, }, `gatsby-transformer-sharp`, `gatsby-plugin-image`, ], 今回は画像の最適化について書いてみました。 次回はコンポーネント化について書いて行きます。
- 投稿日:2021-08-09T14:39:50+09:00
Node.jsで簡易なWebシステムの構築②
目的 Node.jsを用いて簡易なWebシステムを構築する。Node.jsで簡易なWebシステムの構築①のアプリの拡張で、今回はデータの登録を可能にする。 環境条件 Node.jsで簡易なWebシステムの構築①で作業した環境をそのまま利用。 構築手順 ec2-userでログイン # rootユーザにスイッチ sudo su - 1.基本的な環境設定 #/opt/nodejsのディレクトリに移動 cd /opt/nodejs express-generatorを用いてアプリケーションのベースを構築。 #expressのインストール npm install express --save npm WARN saveError ENOENT: no such file or directory, open '/opt/nodejs/package.json' npm notice created a lockfile as package-lock.json. You should commit this file. npm WARN enoent ENOENT: no such file or directory, open '/opt/nodejs/package.json' npm WARN nodejs No description npm WARN nodejs No repository field. npm WARN nodejs No README data npm WARN nodejs No license field. express@4.17.1 added 50 packages from 37 contributors and audited 50 packages in 2.319s found 0 vulnerabilities #express-generatorのインストール npm install -g express-generator npm WARN deprecated mkdirp@0.5.1: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) /usr/local/bin/express -> /usr/local/lib/node_modules/express-generator/bin/express-cli.js + express-generator@4.16.1 added 10 packages from 13 contributors in 0.715s #myapp2という名前でアプリケーションのベースを構築 express -e myapp2 warning: option --ejs' has been renamed to--view=ejs' create : myapp2/ create : myapp2/public/ create : myapp2/public/javascripts/ create : myapp2/public/images/ create : myapp2/public/stylesheets/ create : myapp2/public/stylesheets/style.css create : myapp2/routes/ create : myapp2/routes/index.js create : myapp2/routes/users.js create : myapp2/views/ create : myapp2/views/error.ejs create : myapp2/views/index.ejs create : myapp2/app.js create : myapp2/package.json create : myapp2/bin/ create : myapp2/bin/www change directory: $ cd myapp2 install dependencies: $ npm install run the app: $ DEBUG=myapp2:* npm start #myapp2ディレクトリに移動 cd myapp2/ #npmパッケージのインストール npm install npm notice created a lockfile as package-lock.json. You should commit this file. added 54 packages from 38 contributors and audited 55 packages in 2.187s found 0 vulnerabilities #mysql関連ライブラリとexpress-validatorのインストール npm install --save mysql express-validator 2.アプリケーションの開発 今回のアプリケーションは、http://ホスト名:3000にアクセスするとNode.jsで簡易なWebシステムの構築①と同様に商品(果物)リストが表示され、その画面上にボタンを追加し、当該ボタンから商品登録画面に遷移し、登録が完了すると、登録後の情報を元の画面を更新して表示するという仕様とする。遷移先のURLはhttp://ホスト名:3000/insertとする。 express-generatorで生成された各種ファイルに対し、追記や変更、ファイル追加を実施することで構築している。 追記・変更したもの public/stylesheets/style.css routes/index.js views/index.ejs 追加したもの views/insert.ejs routes/index.js // 必要なライブラリの呼び出し const express = require('express'); const router = express.Router(); const mysql = require('mysql'); const { check, validationResult } = require('express-validator/check'); // mysql接続用の設定定義 const mysql_setting = { host: 'localhost', user: 'root', password: 'password', database: 'myappdb' } // http://<hostname>:3000にアクセスがきた際のレスポンス router.get('/', function(req, res, next) { // DBコネクションの生成 const connection = mysql.createConnection(mysql_setting); connection.connect(); // SQLの実行と結果の取得とindex.ejsへの伝達 connection.query('select * from myapptbl1', function (err, results, fields) { if (err) throw err res.render('index', { content: results }) }); // DBコネクションの破棄 connection.end(); }); // http://<hostname>:3000/insertにアクセスがきた際のレスポンス(insert.ejs) router.get('/insert', function (req, res, next) { const data = { errorMessage: '' } res.render('./insert', data); }); // http://<hostname>:3000/insertへアクセスが来た際のレスポンス // web画面上で入力された値が空になっていないかを確認し、エラーメッセージを表示 router.post('/insert', [check('name').not().isEmpty().trim().escape().withMessage('名前を入力して下さい'),check('price').not().isEmpty().trim().escape().withMessage('値段を入力して下さい'),], (req, res, next) => { const errors = validationResult(req); // 値が空の場合 if (!errors.isEmpty()) { const errors_array = errors.array(); res.render('./insert', { errorMessage: errors_array, }) } else { // 値が入力されている場合 // 画面上で入力された値を変数として定義 const name = req.body.name; const price = req.body.price; // SQL用に配列の作成と変数の入力 const post = { 'name': name, 'price': price }; // DBコネクションの生成 const connection = mysql.createConnection(mysql_setting); connection.connect(); // プレースホルダを用いてSQLを発行し、データを登録する connection.query('INSERT INTO myapptbl1 SET ?', post, function (error, results, fields) { if (error) throw error; // http://<ホスト名>:3000にリダイレクト(Insert後のデータを出力) res.redirect('./'); console.log('ID:', results.insertId); }); // DBコネクションの破棄 connection.end(); } }) module.exports = router; 以下、ejsファイルは大したこと書いてないので、細かい説明は割愛 views/index.ejs <!DOCTYPE html> <html> <head> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <table> <thead> <tr> <th scope="col">id</th> <th scope="col">name</th> <th scope="col">price</th> </tr> </thead> <% for(let i in content) { %> <tr> <% let obj = content[i]; %> <th> <%= obj.id %> </th> <th> <%= obj.name %> </th> <th> <%= obj.price %> </th> </tr> <% } %> </table> <p class="bottom_space"></p> <button class="b1" onclick="location.href='./insert'">商品登録画面</button> </body> </html> views/insert.ejs <!DOCTYPE html> <html> <head> <link rel='stylesheet' href='/stylesheets/style.css' /> </head> <body> <p class="p1">商品を登録してください</p> <form action="/insert" method="post"> <input type="text" name="name" value="名前"> <input type="text" name="price" value="値段"> <button class="b2">登録</button> </form> <% if(errorMessage) { %> <ul> <% for (let n in errorMessage) { %> <li> <%= errorMessage[n].msg %> </li> <% } %> </ul> <% } %> </body> </html> 最低限の見栄えのために以下追記 (aタグまではデフォルト) public/stylesheets/style.css body { padding: 50px; font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; } a { color: #00B7FF; } table, tr, th { border-collapse: collapse; border:1px solid; font-size: 30px; } th { width: 400px; height: 50px; } table thead tr th { color: #fff; background: #000000; } .bottom_space { margin-bottom: 1em; } .b1 { width: 1204px; height: 50px; font-size: 30px; } .b2 { width: 400px; height: 50px; font-size: 30px; } .p1 { font-size: 50px; } input { width: 400px; height: 50px; font-size: 30px; } 3.画面イメージ http://<ホスト名>:3000 http://<ホスト名>:3000/insert 値の入力 登録後のリダイレクト
- 投稿日:2021-08-09T14:16:25+09:00
Chrome 拡張機能を作って公開しよう 最終回 〜Web ストアに公開〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 拡張機能を作るシリーズ、最終回!いよいよ、拡張機能を Web ストアに公開していきます! 開発者登録 まずは、Web ストアにアクセスします。 アクセスしたら、拡張機能を公開する Google アカウントにログインし、「デベロッパーダッシュボード」に移動してください。 本人確認を求められたら、指示に従って本人確認を行ってください。 余談ですが、Google アカウントはサイバー攻撃の対象にされやすいので、複雑なパスワードに加えて2段階認証をかけておくことを強くおすすめします。 このような画面になったら、デベロッパー契約とプライバシー ポリシーを確認し、同意する場合はチェックボックスにチェックを入れてください。 その後、画面右側の「登録料の支払い」をクリックしてください。 Web ストアでは、悪意のある開発者の登録を防ぐため、5ドルの登録料が必要になります。この支払いは、登録時の1回のみで、拡張機能をアップデートする際や新たな拡張機能をリリースする際に支払いを求められることはありません。 プロフィール登録 左側のタブから、「アカウント」をクリックしてください。 そうしたら、「投稿者の表示名」と「メールアドレス」を入力してください。これらは公開されるので、公開しても良い名前(偽名でも OK)・メールアドレス(メールアドレスは正確に)にしてください。 プライバシーポリシーがある場合は、3番目のテキストボックスに、その URL を入力してください。(これは任意です。) 下の方にスクロールしていくと、「通知」という設定項目があります。これをすべてオンにしておくと、拡張機能が公開された時や拡張機能にレビューが付いた時などに通知を受け取れるので便利です。 設定を終えたら、一番下にある 「変更を保存」をクリック してください。 拡張機能のアップロード 「投稿者の設定が正常に更新されました」と表示されたら、左側のタブから「アイテム」をクリックしてください。 ここでは、拡張機能の公開・公開した拡張機能の管理などを行うことができます。 まずは、「新しいアイテム」ボタンをクリックしてください。 「ZIP ファイルをここにドロップするか、ファイルを選択します。」とあるので、拡張機能の開発用フォルダを .zip 形式に圧縮したものをこの枠の中にドラッグ&ドロップします。 .zip ファイルへの圧縮は、開発用フォルダの中にある8個のファイルを全選択して、右クリック→「送信」→「圧縮 (zip 形式) フォルダー」のように選んでいくと行えます。 Windows での手順 Mac での手順 Chromebook での手順 アップロードが完了すると、拡張機能に関する情報を入力する画面に遷移します。 ※「パッケージのタイトル」や「パッケージの概要」が途中までしか表示されていませんが、横にスクロールするとすべて見られます。分かりにくいですね。 必要事項の入力 画面右上に「下書きとして保存する」ボタンがあるので、こまめに保存しておくとトラブルを回避できます。 また、その左側に「送信できない理由」というボタンがあるので、そこをクリックすると、何が不足しているのかがわかります。 ※「送信できない理由」は、下書きとして保存したあとに、最新の状態が反映されます。 「プレビュー」をクリックすると、実際のWebストアの画面が開き、どのように拡張機能が公開されるのかがイメージしやすくなります。 それでは、拡張機能を公開する上での必要事項を入力していきます。 どのような感じで書くのかを説明しますが、具体的な内容はご自身でお考えください。 説明 拡張機能に関する説明。できるだけ詳しく書いたほうが良いです。GitHub などでコードを公開している場合は、その URL も入れるとさらに良いです。なお、「パッケージの概要」の内容は公開時に自動で説明欄に追加されるので、同じ内容を説明欄に書く必要はありません。 カテゴリ この拡張機能がどのカテゴリに属すかを選んでください。Web ストア上の似たような拡張機能を見てみると、「仕事効率化」や「ユーザー補助機能」を選んでいるものが多かったです。 言語 拡張機能で使われている言語。基本的に日本語(?) ショップアイコン Web ストアで表示されるアイコン。128ピクセル×128ピクセル。 全言語向けプロモーション動画 プロモーション動画を YouTube にアップロードしている場合は、その URL を入力してください。 任意 スクリーンショット この拡張機能がどのようなものかがわかる画像。最低1枚、最大5枚まで指定可能。画像サイズに指定があるので、よくご確認ください。 ※プロモーション画像のようにするにも、可能な限りスクリーンショットは入れたほうがよいです。 プロモーションタイトル(小)・(大)、マーキープロモーションタイトル 検索結果のページなどに表示される画像。こちらも画像サイズに指定があるので、よくご確認ください。 任意 追加フィールド ホームページの URL など、必要に応じて入力してください。 任意 ここまで書けたら、下書きとして保存した後、左側のタブから「プライバシーへの取り組み」をクリックしてください。 単一用途の目的 「要するにこの拡張機能の目的はなにか」を書きます。 権限が必要な理由 その権限をどのように利用するのかを書きます。 下に書いてあるものはあくまでも参考程度にし、より詳しく記述するようにしてください。 activeTab ユーザーが閲覧しているタブのID、タイトル、URLを取得するため scripting URLやページタイトル、あるいはその両方をクリップボードにコピーするスクリプトや、各種 SNS の投稿画面をポップアップで開くスクリプトを挿入するため contextMenus コンテキストメニューからもページを共有できるようにするため リモートコード 外部から、JavaScript、CSS、フォントを読み込んでいるか。 1つでも読み込んでいる場合は「はい」を選択し、なぜ外部から読み込む必要があるのかを記述してください。 データ使用 ユーザーのどのようなデータを開発者が得るのか、当てはまるものすべてにチェックを入れてください。 下の方に、3つのチェックボックスがあるので、「デベロッパー プログラム ポリシー」を遵守している場合は、チェックを入れてください。 ここまで入力できたら、下書きとして保存した後、左側のタブから「決済と配布地域」をクリックし、課金要素の有無・公開範囲・配布地域を設定してください。 ただ、このブログを見た方が全員「一般公開」にしてしまうと、類似品が複数出回ることになるので「限定公開」か「非公開」にしてください。 もし一般公開したい場合は、アイコンやデザイン、機能に、元がわからない程度のオリジナリティを加えるようお願いします。 「決済と配布地域」の設定も終えたら、もう一度下書きとして保存してください。 最後にもう一度見直して、不備がなければ「審査のため送信」をクリックしてください。 送信確認画面が現れます。 また、審査終了後に自動で公開するか否かを選択できます。お好きな方を選んでください。 再度「審査のため送信」をクリックすると、拡張機能が審査に提出されます。 これで、早くてその日のうちに、場合によっては数週間後に拡張機能の審査が終わります。 先程、審査合格後に手動で拡張機能を公開することを選んだ方は、審査合格のメールが来たら30日以内にデベロッパーダッシュボードから拡張機能を公開してください。 おわりに この連載も、ついに終わりを迎えます。 Chrome 拡張機能づくりの一連の流れがわかっていただけたのであれば、これ以上の喜びはありません。 これにて、連載企画「Chrome 拡張機能を作って公開しよう」を終わります。本当にお疲れさまでした! 【連載一覧】 環境構築 ポップアップ作成 仕様に沿って開発 コピー機能実装 権限を減らす コンテキストメニューの作成 Webストアに公開 (←今ココ)
- 投稿日:2021-08-09T14:15:59+09:00
Chrome 拡張機能を作って公開しよう⑥ 〜コンテキストメニューを作る〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 1回15分で拡張機能を作るシリーズ、第6弾! 拡張機能も完成に近づいてきました!今回は、更に利便性を向上させるため、コンテキストメニュー(右クリックで開けるメニュー)からも拡張機能を起動できるようにします。 background.js の作成 開発用フォルダの一番上の階層に、background.js を作成してください。 これは、バックグラウンドで動く JavaScript ファイルで、ここからコンテキストメニューを作成することができます。 また、これを使用するにあたって、manifest.json の変更が必要になります。 manifest.json の書き換え manifest.json の permissions を以下のように書き換えてください。 manifest.json "permissions": ["activeTab","scripting","contextMenus"], scripting は、background.js が、表示しているページに対してスクリプトを実行するために必要な権限です。通常、background.js は DOM にアクセスできないのですが、この権限を追加することにより、多少不便ではありますが、DOM にアクセスすることが可能になります。 contextMenus は、コンテキストメニューを使用するために必要な権限です。 また、以下の内容を追記してください。background.js を使用することを明示します。 manifest.json "background": { "service_worker": "background.js" } 全体で以下のようになります。 manifest.json { "name": "タイトルとURLをコピー", "description": "ページのタイトルとURLを簡単にコピーできます。", "version": "1.0", "manifest_version": 3, "icons": { "16": "icon_16x16.png", "48": "icon_48x48.png", "128": "icon_128x128.png" }, "permissions": ["activeTab","scripting","contextMenus"], "action": { "default_title": "タイトルとURLをコピー", "default_icon": "icon_48x48.png", "default_popup": "popup.html" }, "background": { "service_worker": "background.js" } } コンテキストメニューの作成 background.js に以下の内容を記述してください。 background.js /* コンテキストメニューを作成 */ const parent = chrome.contextMenus.create({ id: "share", title: "ページを共有", contexts: ["all"], }); ここまでできたら、コードを保存して、chrome://extensions を開き、更新ボタンをクリックしてください。 ※以下、この操作を「拡張機能を更新する」と表現します。 下の画像のように、コンテキストメニューに「ページを共有」という項目が現れたでしょうか。 もしも現れない場合は、 chrome://extensions の更新ボタンを押したか 書いたプログラムを保存したか manifest.json の記述は正しいか を確認してみてください。 コードの解説 chrome.contextMenus.create でコンテキストメニューを作成することができます。また、この時にコンテキストメニューの設定をオブジェクト形式で行います。 項目 説明 id 後で参照する時に用いる ID title コンテキストメニューに表示する文字列 contexts どこを右クリックした際のコンテキストメニューに追加するか。詳細情報 子コンテキストメニューを作成する それでは、コンテキストメニューの一つ下の階層を作っていきましょう。 やり方は簡単。先程のコードに parentId プロパティを追加して、親コンテキストメニューを指定するだけです。 以下のコードを追加してください。 background.js chrome.contextMenus.create({ parentId: parent, id: "title", title: "ページタイトルをコピー", contexts: ["all"], }); chrome.contextMenus.create({ parentId: parent, id: "URL", title: "URL をコピー", contexts: ["all"], }); chrome.contextMenus.create({ parentId: parent, id: "both", title: "ページタイトルと URL をコピー", contexts: ["all"], }); chrome.contextMenus.create({ parentId: parent, id: "FB", title: "Facebook でシェア", contexts: ["all"], }); chrome.contextMenus.create({ parentId: parent, id: "tweet", title: "ツイート", contexts: ["all"], }); chrome.contextMenus.create({ parentId: parent, id: "LINE", title: "LINE で送る", contexts: ["all"], }); ここまでできたら、拡張機能を更新してください。 下の画像のようなコンテキストメニューになったでしょうか。 動作をつける background.js に以下のコードを追加してください。 background.js /* コンテキストメニューがクリックされた時の処理 */ chrome.contextMenus.onClicked.addListener((info, tab) => { switch (info.menuItemId) { case "title": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: title, }); break; case "URL": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: URL, }); break; case "both": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: both, }); break; case "FB": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: FB, }); break; case "tweet": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: tweet, }); break; case "LINE": chrome.scripting.executeScript({ target: { tabId: tab.id }, function: LINE, }); break; } }); function title() { const element = document.createElement("textarea"); element.value = document.title; document.body.append(element); element.select(); document.execCommand("copy"); element.remove(); } function URL() { const element = document.createElement("textarea"); element.value = location.href; document.body.append(element); element.select(); document.execCommand("copy"); element.remove(); } function both() { const element = document.createElement("textarea"); element.value = document.title + "\n" + location.href; document.body.append(element); element.select(); document.execCommand("copy"); element.remove(); } function FB() { window.open( "https://www.facebook.com/share.php?u=" + encodeURIComponent(location.href), "tweetwindow", "width=650, height=470, personalbar=0, toolbar=0, scrollbars=1, sizable=1" ); } function tweet() { window.open( "https://twitter.com/intent/tweet?text=" + encodeURIComponent(document.title) + "%0a&url=" + encodeURIComponent(location.href), "tweetwindow", "width=650, height=470, personalbar=0, toolbar=0, scrollbars=1, sizable=1" ); } function LINE() { window.open( "https://social-plugins.line.me/lineit/share?url=" + encodeURIComponent(location.href), "tweetwindow", "width=650, height=470, personalbar=0, toolbar=0, scrollbars=1, sizable=1" ); } コードの解説 chrome.contextMenus.onClicked.addListener((info, tab) => {}) で、コンテキストメニューがクリックされた際の処理を指定できます。 引数の info にクリックされたコンテキストメニューの情報が、tab に現在のタブの情報が格納されています。 info.menuItemId に、先程コンテキストメニューを作成した際に指定した ID が格納されているので、それで処理を分けます。 switch 文は if ... else if ... else if ... else ... と同じような処理が簡単に行える文です。 詳細情報 chrome.scripting.executeScript で、指定したタブに指定した JavaScript のコードを挿入できます。 これを用いることで、background.js から DOM を操作することができます。 タブを指定する際にはタブの ID を用いるのですが、これは chrome.contextMenus.onClicked.addListener((info, tab) => {}) の引数 tab 内に格納されているため、tab.id で参照できます。 これが target: { tabId: tab.id } の部分に当たります。 function: で、挿入する JavaScript の関数を指定します。 これで、拡張機能を更新すると、コンテキストメニューからも各種操作が行えるようになります。 まとめ background.js でコンテキストメニューを作成できる。 background.js から DOM を操作するには、scripting 権限が必要である。chrome.contextMenus.onClicked.addListener((info, tab) => {}) で JavaScript コードを指定のタブに挿入できる。 今回扱ったコードは こちら から見られます。ぜひ参考にしてみてください。 次回予告 次回はいよいよ、作成した拡張機能を Web ストアに出品していきます。 以下のものが必要になるので、次回までに用意しておいてください。 Google アカウント(できれば開発用に新しく作ったほうが良い1) 5ドル(Web ストアに開発者登録をする際に必要) クレジットカードまたはデビットカード 本人確認を済ませた LINE プリペイドカードでも可 お疲れさまでした! 【連載一覧】 環境構築 ポップアップ作成 仕様に沿って開発 コピー機能実装 権限を減らす コンテキストメニューの作成 (←今ココ) Webストアに公開 問い合わせ用のメールアドレスが必要になります。これは公開されるので、本名を隠したい人は匿名の Google アカウントを用意しておいてください。 ↩
- 投稿日:2021-08-09T14:15:47+09:00
Chrome 拡張機能を作って公開しよう⑤ 〜権限多すぎ問題を解決〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 権限を少なくする 前回まで書いてきたコードですが、まだ課題があります。 これは、Chrome の拡張機能の管理画面のスクリーンショットです。権限が、「閲覧履歴の読み取り」と、この拡張機能の機能に対して過大であることがわかります。 今回は、これを修正します。 やり方は簡単です。manifest.json の permissions を以下のように書き換えてください。 manifest.json "permissions": ["activeTab"], chrome://extensions を開き、更新ボタンを押します。 「詳細」ボタンをクリックして、「権限」欄を見てください。 「この拡張機能に特別な権限は必要ありません」と表示されていれば OK です。 不具合を修正する しかし、これだけで今日の記事が終わってしまってはあっけないので、もう一つやります。 chrome://extension 画面で拡張機能を開き、Facebook でシェアしてみてください。 エラーになります。どうやら、URL に http:// か https:// が含まれていないとリクエストを拒否する仕様になっているようです。 そこで、URL にこれらが存在するか調べる機能を実装していきます。 HTML の修正 「Facebook のシェアボタン」〜「LINE で送るボタン」までを、<div id="sns">で囲んでください。 JavaScript の修正 popup.js の chrome.tabs.query の中の末尾に以下のコードを追加してください。 popup.js if(!/http\:\/\/|https\:\/\//.test(tabUrl)) { const element = document.getElementById("sns"); element.style.marginTop = "1.5em"; element.textContent = "このページでは SNS を用いたシェア機能をご利用になれません。ご了承ください。"; } このコードに登場する test は、とある文字列の中に指定した文字列が含まれているかを、true または false で返すものです。 test の前に検索したい文字列を正規表現で指定します。正規表現について、詳しくは説明しませんが、このコードに関わっているルールを説明します。 正規表現は2つの / (スラッシュ)の間に書く | は「または」という意味。/りんご|みかん/ と書いた場合、「りんご」または「みかん」が含まれているか、という意味となる 正規表現の中に登場する /(スラッシュ)や:(コロン)、;(セミコロン)が含まれている場合、これらの文字がプログラムのコマンドと誤認識されないよう、直前に \(バッククオート)を入れる。 上記のコードの場合、「http:// か https:// が含まれているか」という意味になる。 もし、http:// も https:// も含まれていない場合、先程 popup.html に追加した<div id="sns"> の上に余白を追加し、中のテキストを変更するようになっています。 これで、SNS でシェアした際にエラーになるのを防ぐことができます。 今日は以上です! まとめ tabs の代わりに activeTab を指定することで、権限が無駄に多くなることが防げる。 test を用いると、文字列を検索することができる。 本日もお疲れさまでした! 次回は Web ストアに出品する予定でしたが、予定を変更してもう一つ機能を実装していきます。 また、今回扱ったコードは、こちらから見られます。 【連載一覧】 環境構築 ポップアップ作成 仕様に沿って開発 コピー機能実装 権限を減らす (←今ココ) コンテキストメニューの作成 Webストアに公開
- 投稿日:2021-08-09T14:15:26+09:00
Chrome 拡張機能を作って公開しよう④ 〜コピー機能の実装〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 こんにちは!1回30分で拡張機能を作るシリーズ、第4弾です。今回は、この拡張機能の要、「コピー機能」を実装していきます。 「あれ?15分じゃなかったのかよ!」と気づかれた方、そうです。1回分のボリュームが15分に収まりきらないということで、誠に勝手ながら30分に延長させていただきました。 では、本題に入っていきましょう。(切り替えが早い) コピーボタンの配置 popup.html を次のように書き換えてください。 <!--ここから--> という部分から、<!--ここまで--> という部分までが追加する分です。追加する部分は2箇所あります。 popup.html <div class="text"> <input type="text" id="pageTitle" class="column" readonly> </div> <!--ここから--> <div class="copy"> <button title="ページタイトルをコピー" id="copyTitle"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16" aria-label="ページタイトルをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> </button> </div> <!--ここまで--> <!--省略--> <div class="text"> <input type="url" id="pageURL" class="column" readonly> </div> <!--ここから--> <div class="copy"> <button title="URLをコピー" id="copyURL"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16" aria-label="URLをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> </button> </div> <!--ここまで--> また、<script> タグの直前に、以下のコードを追記してください。 popup.html <div class="content"> <div class="both"> <button title="タイトルとURLをコピー" id="copyBoth"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard withText" viewBox="0 0 16 16" aria-label="タイトルとURLをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg>タイトルと URL をコピー </button> </div> </div> このままではボタンがむき出しになっていてかっこ悪いので、popup.css に以下の内容を追記してください。 一応、コメントに解説のようなものを載せました。よかったら参考にしてください。 popup.css /*ボタンに適用するCSS*/ button { appearance: none; /*デフォルトの見た目を無効に*/ -webkit-appearance: none; /*あまり意味はないが、古いブラウザ向け。デフォルトの見た目を無効にする。*/ border: 0; /*枠線を消す*/ background-color: #4c3cda; /*背景色を指定*/ color: #fff;/*文字色を白に*/ padding: 8px 16px; /*内側の余白を指定*/ font-size: 16px; /*フォントサイズを16pxに*/ border-radius: 5px; /*角を丸くする*/ cursor: pointer; /*カーソルを手のひらの形状にする*/ transition: background-color .2s; /*背景色が変わるときにアニメーション*/ } button:hover { /*カーソルがボタンに乗っているとき*/ background-color: #2f22a3; /*背景色を変更*/ } button:focus { /*tabキーなどで、ボタンがフォーカスされているとき*/ outline: none; /*デフォルトの枠線を消す*/ box-shadow: 0 0 0 4px #cbd6ee; /*代わりの枠線*/ } /*ボタンの中のアイコンに適用するCSS*/ button svg { fill: #fff; /*ボタンの中のアイコンの色を白色にする*/ } svg.withText{ vertical-align: middle; /*隣接するテキストを、アイコンの上下中央にラインに配置*/ margin-right: .7em; /*右側に余白をあける*/ } ここまでやると、以下の画像のようになります。ボタンにスタイルが適用されていて、いい感じですね! コピー機能の実装 それでは、本題の「コピー機能の実装」に移ります。 流れとしては、 1. コピーボタンをクリックする 2. クリップボードに URL またはタイトルがコピーされる 「コピーボタンをクリックしたとき」は、addEventListener を使えばいけそうです。 また、クリップボードにコピーするという動作は、navigator.clipboard.writeText(コピーしたい文字列) でできます。 それでは、これを組み合わせてみましょう。popup.js に、以下のようにコードを追加してください。/*ここから*/ から、/*ここまで*/ の部分が追加するコードです。 popup.js chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, tabs => { title.value = tabs[0].title; url.value = tabs[0].url; /*ここから*/ document.getElementById("copyTitle").addEventListener("click",()=>{ navigator.clipboard.writeText(tabs[0].title); }, false); document.getElementById("copyURL").addEventListener("click",()=>{ navigator.clipboard.writeText(tabs[0].url); }, false); document.getElementById("copyBoth").addEventListener("click",()=>{ navigator.clipboard.writeText(tabs[0].title + "\n" + tabs[0].url); }, false); /*ここまで*/ }); それでは、「コピー」ボタンを押して、動作を確認してみてください! ちなみに、「タイトルとURLを両方コピー」した場合は、タイトルとURLの間に改行が入ります。これが、上記のコードの \n に当たる部分です。JavaScriptでは、\n を使うと文字列を改行させることができます。 コピーしたことを伝える しかし、これではボタンを押しても何も起こらないので、本当にコピーされたのか疑問に思いますよね。 そこで、コピーボタンを押したら、クリップボードのアイコンが「チェックアイコン」に変わるようにしましょう。 仕組みはこうです。 コピーボタンが押されたら、クリップボードのアイコンを非表示にし、チェックアイコンを表示する 5秒後にもとに戻す ボタンがクリックされたとき、他のボタンがチェックアイコンだったらもとに戻す まずは、popup.html から変更していきます。すべてのクリップボードアイコンのあとに、以下のコードを追加してください。(クリップボードアイコンには、bi-clipboard というクラス名がついています。目印にしてください。) popup.html <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2 checkIcon" viewBox="0 0 16 16" style="display: none;"> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> </svg> ただ、この時点でポップアップを開いてもなんの変化もありません。先程追加したチェックアイコンには、style="display: none;" とあります。これにより、非表示になっているからです。 では、popup.js に、ボタンを押したときにチェックアイコンを表示させるようにプログラムします。 「クリップボードアイコンをチェックアイコンに入れ替える」という処理は何度か使いそうなので、関数にまとめます。 popup.js の末尾に以下のコードを追加してください。 popup.js let timeout; function showCheck(parentId) { clearTimeout(timeout); //5秒後に予定されている処理をキャンセル const checkIcons = document.getElementsByClassName("checkIcon"); for (let i = 0; i < checkIcons.length; i++) { checkIcons[i].style.display = "none"; //一旦すべてのチェックアイコンを非表示に } const clipIcons = document.getElementsByClassName("bi-clipboard"); for (let i = 0; i < clipIcons.length; i++) { clipIcons[i].style.display = ""; //一旦すべてのクリップボードアイコンを表示 } const child = document.getElementById(parentId).children; //ボタンの子要素を取得 child[0].style.display = "none"; //ボタンの子要素のうち1番目、つまりクリップボードアイコンを非表示に child[1].style.display = ""; //ボタンの子要素のうち2番目、つまりチェックアイコンを表示する timeout = setTimeout(() => { /*5秒後の処理*/ child[0].style.display = ""; //クリップボードアイコンを表示する child[1].style.display = "none"; //チェックアイコンを非表示に }, 5000); } そして、ボタンがクリックされた際にこの関数を呼び出すようにします。引数 parentId には、ボタンのIDを代入します。 navigator.clipboard.writeText() の次の行に、下記の「候補1」〜「候補3」のいずれかを追加してください。どの navigator.clipboard.writeText() の後に、どのコードを追加するのかは、是非自分で考えてみてください! popup.js /*候補1*/ showCheck("copyTitle"); /*候補2*/ showCheck("copyURL"); /*候補3*/ showCheck("copyBoth"); これで、コピーボタンをクリックするとチェックアイコンに切り替わるはずです。 もし、クリックしたのと違うボタンのアイコンが変化している場合は、上記のコードを追加する場所を間違えています。 SNS へのシェアボタンを追加する SNS で直接投稿・送信ができるボタンを設置します。 このようなボタンについては、公式が「このURLにアクセスすればシェアできるよ」と公開してくれているので、それに則って作成していきます。 まずは popup.html から行きましょう。 <script> タグの直前にある <div class="content"> の中身をすべて削除してください。そして、以下のコードに書き換えてください。 popup.html <div class="both"> <button title="タイトルとURLをコピー" id="copyBoth" class="shareSNS copyBoth"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard withText" viewBox="0 0 16 16" aria-label="タイトルとURLをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2 checkIcon withText" viewBox="0 0 16 16" style="display: none;"> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> </svg> タイトルと URL をコピー </button> </div> <div class="toFB"> <button title="シェア" id="shareToFB" class="FB shareSNS"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-facebook withText" viewBox="0 0 16 16" aria-label="Facebook"> <path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/> </svg> シェア </button> </div> <div class="toTwitter"> <button title="ツイート" id="tweet" class="twitter shareSNS"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter withText" viewBox="0 0 16 16" aria-label="Twitter"> <path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/> </svg> ツイート </button> </div> <div class="toLINE"> <button title="LINE で送る" id="LINE" class="LINE shareSNS"> <svg class="withText" xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 448 512" aria-label="LINEで送る"> <!-- Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --> <path d="M272.1 204.2v71.1c0 1.8-1.4 3.2-3.2 3.2h-11.4c-1.1 0-2.1-.6-2.6-1.3l-32.6-44v42.2c0 1.8-1.4 3.2-3.2 3.2h-11.4c-1.8 0-3.2-1.4-3.2-3.2v-71.1c0-1.8 1.4-3.2 3.2-3.2H219c1 0 2.1.5 2.6 1.4l32.6 44v-42.2c0-1.8 1.4-3.2 3.2-3.2h11.4c1.8-.1 3.3 1.4 3.3 3.1zm-82-3.2h-11.4c-1.8 0-3.2 1.4-3.2 3.2v71.1c0 1.8 1.4 3.2 3.2 3.2h11.4c1.8 0 3.2-1.4 3.2-3.2v-71.1c0-1.7-1.4-3.2-3.2-3.2zm-27.5 59.6h-31.1v-56.4c0-1.8-1.4-3.2-3.2-3.2h-11.4c-1.8 0-3.2 1.4-3.2 3.2v71.1c0 .9.3 1.6.9 2.2.6.5 1.3.9 2.2.9h45.7c1.8 0 3.2-1.4 3.2-3.2v-11.4c0-1.7-1.4-3.2-3.1-3.2zM332.1 201h-45.7c-1.7 0-3.2 1.4-3.2 3.2v71.1c0 1.7 1.4 3.2 3.2 3.2h45.7c1.8 0 3.2-1.4 3.2-3.2v-11.4c0-1.8-1.4-3.2-3.2-3.2H301v-12h31.1c1.8 0 3.2-1.4 3.2-3.2V234c0-1.8-1.4-3.2-3.2-3.2H301v-12h31.1c1.8 0 3.2-1.4 3.2-3.2v-11.4c-.1-1.7-1.5-3.2-3.2-3.2zM448 113.7V399c-.1 44.8-36.8 81.1-81.7 81H81c-44.8-.1-81.1-36.9-81-81.7V113c.1-44.8 36.9-81.1 81.7-81H367c44.8.1 81.1 36.8 81 81.7zm-61.6 122.6c0-73-73.2-132.4-163.1-132.4-89.9 0-163.1 59.4-163.1 132.4 0 65.4 58 120.2 136.4 130.6 19.1 4.1 16.9 11.1 12.6 36.8-.7 4.1-3.3 16.1 14.1 8.8 17.4-7.3 93.9-55.3 128.2-94.7 23.6-26 34.9-52.3 34.9-81.5z"></path> </svg> LINE で送る </button> </div> しかし、これではレイアウトやデザインがメチャクチャなので、CSS で整えます。popup.css の末尾に以下のコードを追加してください。 popup.css .shareSNS{ background-color: #fff; width: 100%; text-align: center; margin: .3em 0; border-width: 2px; border-style: solid; } .copyBoth{ color: #4c3cda; border-color: #4c3cda; } .copyBoth svg{ fill: #4c3cda; } .copyBoth:hover{ background-color: #4c3cda; } .FB{ color: #1877f2; border-color: #1877f2; } .FB svg{ fill: #1877f2; } .FB:hover{ background-color: #1877f2; } .twitter{ color: #1DA1F2; border-color: #1DA1F2; } .twitter svg{ fill: #1DA1F2; } .twitter:hover{ background-color: #1DA1F2; } .LINE{ color: #00B900; border-color: #00B900; } .LINE svg{ fill: #00B900; } .LINE:hover{ background-color: #00B900; } .shareSNS:hover{ color: #fff; } .shareSNS:hover svg{ fill: #fff; } 下の画像のようになっていればOKです。 では、popup.js で動きを付け加えていきます。 これと同時に、tabs[0].title と tabs[0].url を定数に格納します。後で参照しやすくするためです。 ということで、chrome.tabs.query の中身の先頭に以下のコードを追加してください。 popup.js const tabTitle = tabs[0].title; const tabUrl = tabs[0].url; そして、コードの中の tabs[0].title と tabs[0].url をそれぞれ、tabTitle と tabUrl に書き換えてください。 その後、以下のコードを chrome.tabs.query の中身の末尾に追加してください。 popup.js /*Facebook*/ document.getElementById("shareToFB").addEventListener("click", () => { windowOpen("https://www.facebook.com/share.php?u=" + encodeURIComponent(tabUrl)); }, false); /*Twitter*/ document.getElementById("tweet").addEventListener("click", () => { windowOpen("https://twitter.com/intent/tweet?text=" + encodeURIComponent(tabTitle) + "%0a&url=" + encodeURIComponent(tabUrl)); }, false); /*LINE*/ document.getElementById("LINE").addEventListener("click", ()=>{ windowOpen("https://social-plugins.line.me/lineit/share?url=" + encodeURIComponent(tabUrl)); }, false); 下の関数も定義してください。これは、ポップアップを開く際に用いられています。引数 url がポップアップを開くURLに当たります。 popup.js function windowOpen(url) { window.open(url, 'tweetwindow', 'width=650, height=470, personalbar=0, toolbar=0, scrollbars=1, sizable=1') this.close(); return false; } では、SNS シェア部分のコードの解説をします。 主要SNS では、指定された URL にアクセスすることで、シェアができるようになっています。SNS サービス毎の URL は以下のとおりです。 サービス名 URL Facebook https://www.facebook.com/share.php?u={{ URL }} Twitter https://twitter.com/share?text={{ ツイート文 }}&url={{ URL }}&hashtags={{ ハッシュタグ (カンマ区切りで複数指定可能) }} LINE https://social-plugins.line.me/lineit/share?url={{ URL }} {{ URL }} の部分に共有したい URL を入れると、上記のような SNS でシェアすることができます。 また、Twitter の場合は、ツイート文やハッシュタグも指定できます。要領は URL と同じで、指定された部分に文字を入れるだけです。 上記のコードの中に、encodeURIComponent() というものが頻繁に使われていますが、これは日本語や記号などを、コンピューターが扱いやすいように変換するためのものです。URL の中に日本語などが紛れていると、予期せぬトラブルの原因になります。そこで、この処理が必要になるわけです。 コード全体 最後に、コードの全体を提示します。「うまく動かない」等の場合は参考にしてみてください。 popup.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="popup.css"> </head> <body> <div class="content"> <label for="pageTitle" class="column">タイトル</label> <div class="grid"> <div class="text"> <input type="text" id="pageTitle" class="column" readonly> </div> <div class="copy"> <button title="ページタイトルをコピー" id="copyTitle"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16" aria-label="ページタイトルをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2 checkIcon" viewBox="0 0 16 16" style="display: none;"> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> </svg> </button> </div> </div> </div> <div class="content"> <label for="pageURL" class="column">URL</label> <div class="grid"> <div class="text"> <input type="url" id="pageURL" class="column" readonly> </div> <div class="copy"> <button title="URLをコピー" id="copyURL"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard" viewBox="0 0 16 16" aria-label="URLをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2 checkIcon" viewBox="0 0 16 16" style="display: none;"> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> </svg> </button> </div> </div> </div> <div class="content"> <div class="both"> <button title="タイトルとURLをコピー" id="copyBoth" class="shareSNS copyBoth"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-clipboard withText" viewBox="0 0 16 16" aria-label="タイトルとURLをコピー"> <path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/> <path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/> </svg> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-check2 checkIcon withText" viewBox="0 0 16 16" style="display: none;"> <path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/> </svg> タイトルと URL をコピー </button> </div> <div class="toFB"> <button title="シェア" id="shareToFB" class="FB shareSNS"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-facebook withText" viewBox="0 0 16 16" aria-label="Facebook"> <path d="M16 8.049c0-4.446-3.582-8.05-8-8.05C3.58 0-.002 3.603-.002 8.05c0 4.017 2.926 7.347 6.75 7.951v-5.625h-2.03V8.05H6.75V6.275c0-2.017 1.195-3.131 3.022-3.131.876 0 1.791.157 1.791.157v1.98h-1.009c-.993 0-1.303.621-1.303 1.258v1.51h2.218l-.354 2.326H9.25V16c3.824-.604 6.75-3.934 6.75-7.951z"/> </svg> シェア </button> </div> <div class="toTwitter"> <button title="ツイート" id="tweet" class="twitter shareSNS"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-twitter withText" viewBox="0 0 16 16" aria-label="Twitter"> <path d="M5.026 15c6.038 0 9.341-5.003 9.341-9.334 0-.14 0-.282-.006-.422A6.685 6.685 0 0 0 16 3.542a6.658 6.658 0 0 1-1.889.518 3.301 3.301 0 0 0 1.447-1.817 6.533 6.533 0 0 1-2.087.793A3.286 3.286 0 0 0 7.875 6.03a9.325 9.325 0 0 1-6.767-3.429 3.289 3.289 0 0 0 1.018 4.382A3.323 3.323 0 0 1 .64 6.575v.045a3.288 3.288 0 0 0 2.632 3.218 3.203 3.203 0 0 1-.865.115 3.23 3.23 0 0 1-.614-.057 3.283 3.283 0 0 0 3.067 2.277A6.588 6.588 0 0 1 .78 13.58a6.32 6.32 0 0 1-.78-.045A9.344 9.344 0 0 0 5.026 15z"/> </svg> ツイート </button> </div> <div class="toLINE"> <button title="LINE で送る" id="LINE" class="LINE shareSNS"> <svg class="withText" xmlns="http://www.w3.org/2000/svg" width="19" height="19" fill="currentColor" viewBox="0 0 448 512" aria-label="LINEで送る"> <!-- Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) --> <path d="M272.1 204.2v71.1c0 1.8-1.4 3.2-3.2 3.2h-11.4c-1.1 0-2.1-.6-2.6-1.3l-32.6-44v42.2c0 1.8-1.4 3.2-3.2 3.2h-11.4c-1.8 0-3.2-1.4-3.2-3.2v-71.1c0-1.8 1.4-3.2 3.2-3.2H219c1 0 2.1.5 2.6 1.4l32.6 44v-42.2c0-1.8 1.4-3.2 3.2-3.2h11.4c1.8-.1 3.3 1.4 3.3 3.1zm-82-3.2h-11.4c-1.8 0-3.2 1.4-3.2 3.2v71.1c0 1.8 1.4 3.2 3.2 3.2h11.4c1.8 0 3.2-1.4 3.2-3.2v-71.1c0-1.7-1.4-3.2-3.2-3.2zm-27.5 59.6h-31.1v-56.4c0-1.8-1.4-3.2-3.2-3.2h-11.4c-1.8 0-3.2 1.4-3.2 3.2v71.1c0 .9.3 1.6.9 2.2.6.5 1.3.9 2.2.9h45.7c1.8 0 3.2-1.4 3.2-3.2v-11.4c0-1.7-1.4-3.2-3.1-3.2zM332.1 201h-45.7c-1.7 0-3.2 1.4-3.2 3.2v71.1c0 1.7 1.4 3.2 3.2 3.2h45.7c1.8 0 3.2-1.4 3.2-3.2v-11.4c0-1.8-1.4-3.2-3.2-3.2H301v-12h31.1c1.8 0 3.2-1.4 3.2-3.2V234c0-1.8-1.4-3.2-3.2-3.2H301v-12h31.1c1.8 0 3.2-1.4 3.2-3.2v-11.4c-.1-1.7-1.5-3.2-3.2-3.2zM448 113.7V399c-.1 44.8-36.8 81.1-81.7 81H81c-44.8-.1-81.1-36.9-81-81.7V113c.1-44.8 36.9-81.1 81.7-81H367c44.8.1 81.1 36.8 81 81.7zm-61.6 122.6c0-73-73.2-132.4-163.1-132.4-89.9 0-163.1 59.4-163.1 132.4 0 65.4 58 120.2 136.4 130.6 19.1 4.1 16.9 11.1 12.6 36.8-.7 4.1-3.3 16.1 14.1 8.8 17.4-7.3 93.9-55.3 128.2-94.7 23.6-26 34.9-52.3 34.9-81.5z"></path> </svg> LINE で送る </button> </div> </div> <script src="popup.js" defer></script> </body> </html> popup.css body { letter-spacing: 0.15em; width: 400px; font-size: 16px; } * { color: #606c76; box-sizing: border-box; font-family: -apple-system, blinkMacSystemFont, "Helvetica Neue", "Segoe UI", "Arial", "Roboto", "Hiragino Kaku Gothic ProN", YuGothicM, YuGothic, Meiryo, "Noto Sans JP", sans-serif; } @font-face { font-family: YuGothicM; font-weight: normal; src: local("YuGothic-Medium"), local("Yu Gothic Medium"), local("YuGothic-Regular"); /* Windows8.1ではMediumがないのでRegularを指定 */ } @font-face { font-family: YuGothicM; font-weight: bold; src: local("YoGothic-Bold"), local("Yu Gothic"); } .content { margin: 2em 0; } .grid { display: grid; grid-template-columns: 1fr auto; gap: 1.2em 1em; width: 100%; margin-top: 0.3em; } .text { position: relative; } .text input { color: #727d86; font: 16px sans-serif; width: 100%; padding: 0; padding-bottom: 0.1em; letter-spacing: 1px; border: 0; } .text input:focus { outline: none; } .text input:focus::after { outline: none; } .text::after { display: block; width: 100%; height: 1px; margin-top: 0; content: ""; border-width: 0 1px 1px 1px; border-style: solid; border-color: #4c3cda; } .text .column { width: 100%; height: 100%; } button { appearance: none; -webkit-appearance: none; border: 0; background-color: #4c3cda; color: #fff; padding: 8px 16px; font-size: 16px; border-radius: 5px; cursor: pointer; transition: background-color .2s; } button:hover { background-color: #2f22a3; } button:focus { outline: none; box-shadow: 0 0 0 4px #cbd6ee; } button svg { fill: #fff; } svg.withText{ vertical-align: middle; margin-right: .7em; } .shareSNS{ background-color: #fff; width: 100%; text-align: center; margin: .3em 0; border-width: 2px; border-style: solid; } .copyBoth{ color: #4c3cda; border-color: #4c3cda; } .copyBoth svg{ fill: #4c3cda; } .copyBoth:hover{ background-color: #4c3cda; } .FB{ color: #1877f2; border-color: #1877f2; } .FB svg{ fill: #1877f2; } .FB:hover{ background-color: #1877f2; } .twitter{ color: #1DA1F2; border-color: #1DA1F2; } .twitter svg{ fill: #1DA1F2; } .twitter:hover{ background-color: #1DA1F2; } .LINE{ color: #00B900; border-color: #00B900; } .LINE svg{ fill: #00B900; } .LINE:hover{ background-color: #00B900; } .shareSNS:hover{ color: #fff; } .shareSNS:hover svg{ fill: #fff; } popup.js const title = document.getElementById("pageTitle"); const url = document.getElementById("pageURL"); chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, tabs => { const tabTitle = tabs[0].title; const tabUrl = tabs[0].url; title.value = tabTitle; url.value = tabUrl; document.getElementById("copyTitle").addEventListener("click", () => { navigator.clipboard.writeText(tabTitle); showCheck("copyTitle"); }, false); document.getElementById("copyURL").addEventListener("click", () => { navigator.clipboard.writeText(tabUrl); showCheck("copyURL"); }, false); document.getElementById("copyBoth").addEventListener("click", () => { navigator.clipboard.writeText(tabTitle + "\n" + tabUrl); showCheck("copyBoth"); }, false); document.getElementById("shareToFB").addEventListener("click", () => { windowOpen("https://www.facebook.com/share.php?u=" + encodeURIComponent(tabUrl)); }, false); document.getElementById("tweet").addEventListener("click", () => { windowOpen("https://twitter.com/intent/tweet?text=" + encodeURIComponent(tabTitle) + "%0a&url=" + encodeURIComponent(tabUrl)); }, false); document.getElementById("LINE").addEventListener("click", ()=>{ windowOpen("https://social-plugins.line.me/lineit/share?url=" + encodeURIComponent(tabUrl)); }, false); }); let timeout; function showCheck(parentId) { clearTimeout(timeout); const checkIcons = document.getElementsByClassName("checkIcon"); for (let i = 0; i < checkIcons.length; i++) { checkIcons[i].style.display = "none"; } const clipIcons = document.getElementsByClassName("bi-clipboard"); for (let i = 0; i < clipIcons.length; i++) { clipIcons[i].style.display = ""; } const child = document.getElementById(parentId).children; child[0].style.display = "none"; child[1].style.display = ""; timeout = setTimeout(() => { child[0].style.display = ""; child[1].style.display = "none"; }, 5000); } function windowOpen(url) { window.open(url, 'tweetwindow', 'width=650, height=470, personalbar=0, toolbar=0, scrollbars=1, sizable=1') this.close(); return false; } さて、今回はここまでです。お疲れさまでした! TODO 前回予告した、「権限を少なくする作業」(次回) Chrome Web ストアで公開(次々回) ※今後の予定は変更になる可能性があります まとめ navigator.clipboard.writeText(コピーしたい文字列) で文字列をクリップボードにコピーできる。 指定のURLにアクセスすれば、各種SNSでシェアすることができる。 今回は前回以上にボリューミーでしたが、ここまで読んでいただきありがとうございました。 また、今回扱ったコードは、こちらから見られます。 【連載一覧】 環境構築 ポップアップ作成 仕様に沿って開発 コピー機能実装 (←今ココ) 権限を減らす コンテキストメニューの作成 Webストアに公開
- 投稿日:2021-08-09T14:15:06+09:00
Chrome 拡張機能を作って公開しよう③ 〜形にする〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 こんにちは。1回15分で拡張機能を作るシリーズ、第3弾です。 今回は、前回作成したファイルをもとに、拡張機能を形にしていきます。 今回はボリュームが大きいので、15分で終わらないかもしれません。その時はごめんなさい。 仕様 まずは今回の拡張機能の仕様を紹介します。 2つの独立したテキストボックスに、URL・ページタイトルをそれぞれ代入する URL・ページタイトルは、それぞれコピーできる URLとページタイトルを両方コピーすることもできる Facebook や Twitter、LINE へのシェア機能も実装する とりあえずこのような仕様でいきます。 ポップアップの作成 前回作ったものは、「Hello, world」と出力するだけのものでしたが、今回は更に拡張機能っぽくしていきます。 なお、今から記述していく HTML と CSS は簡単なものなのでコピペで済ませて構いません。後半の JavaScript が難しいので... では、まずは HTML から。popup.htmlの中身をすべて消して、以下の内容を記述してください。 popup.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="popup.css"> </head> <body> <div class="content"> <label for="pageTitle" class="column">タイトル</label> <div class="grid"> <div class="text"> <input type="text" id="pageTitle" class="column" readonly> </div> </div> </div> <div class="content"> <label for="pageURL" class="column">URL</label> <div class="grid"> <div class="text"> <input type="url" id="pageURL" class="column" readonly> </div> </div> </div> <script src="popup.js" defer></script> </body> </html> HTML を保存したら、ポップアップを開いてみてください。 以下のようになっていれば OK です。 しかし、これでは味気ないので、CSS の登場です!popup.cssの中身をすべて消して、以下の内容を記述してください。 popup.css body { letter-spacing: .15em; width: 400px; font-size: 16px; } * { color: #606c76; box-sizing: border-box; font-family: -apple-system, blinkMacSystemFont, "Helvetica Neue", "Segoe UI", "Arial", "Roboto", "Hiragino Kaku Gothic ProN", YuGothicM, YuGothic, Meiryo, 'Noto Sans JP', sans-serif; } @font-face { font-family: YuGothicM; font-weight: normal; src: local("YuGothic-Medium"), local("Yu Gothic Medium"), local("YuGothic-Regular"); /* Windows8.1ではMediumがないのでRegularを指定 */ } @font-face { font-family: YuGothicM; font-weight: bold; src: local("YoGothic-Bold"), local("Yu Gothic"); } .content{ margin: 2em 0; } .grid { display: grid; grid-template-columns: 1fr auto; gap: 1.2em 1em; width: 100%; margin-top: .3em; } .text { position: relative; } .text input { color: #727d86; font: 16px sans-serif; width: 100%; padding: 0; padding-bottom: .1em; letter-spacing: 1px; border: 0; } .text input:focus { outline: none; } .text input:focus::after { outline: none; } .text::after { display: block; width: 100%; height: 1px; margin-top: 0; content: ''; border-width: 0 1px 1px 1px; border-style: solid; border-color: #4c3cda; } .text .column { width: 100%; height: 100%; } これでポップアップを開いてみると...? いい感じになっています! JavaScript で動きをつける さあ、続いて JavaScript の記述です!まずは、popup.jsの中身を消して、「タイトル」を表示させる要素と「URL」を表示させる要素を取得して定数に代入します。 popup.js const title = document.getElementById("pageTitle"); const url = document.getElementById("pageURL"); テキストボックスにテキストを代入するには、element.value = (代入したい文字列) を使えばよいのでした。きっと多くの人が下記のプログラムを思いついたでしょう。 const title = document.getElementById("pageTitle"); const url = document.getElementById("pageURL"); /* ココから追記分 */ title.value = document.title;// タイトルを代入 url.value = location.href; //URLを代入 /* ココまで追記分 */ しかし、実行してみると... このように、このポップアップの HTML の URL とタイトルが代入されてしまいます。 (URL は人によって異なるかもしれません。また、ポップアップに <title> タグが指定されていないため、「タイトル」欄は空白になっています。) そこで、用いるのが、chrome.tabs.queryメソッドです!これは、Chrome の拡張機能のみが使用することができるメソッドとなっています。 このメソッドを使うためには、manifest.jsonに以下の内容を書き加えて、「この拡張機能は開いているタブの情報を必要とするから教えてくれ」ということをブラウザに伝える必要があります。 manifest.json "permissions": [ "tabs" ], このメソッドを用いてプログラムを書き換えると、以下のようになります。「ここは削除!」とコメントしてある部分は、削除してください。 popup.js const title = document.getElementById("pageTitle"); const url = document.getElementById("pageURL"); /*========================= ここは削除! title.value = document.title;// タイトルを代入 url.value = location.href; //URLを代入 ===========================*/ /*ココから追記分*/ chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, tabs => { title.value = tabs[0].title; url.value = tabs[0].url; }); では、chrome.tabs.query について詳しく説明していきます。 chrome.tabs.queryは、条件に合うタブの情報を、NodeList 形式で返してくれるメソッドです。NodeList がよくわからない人は、普通の JavaScript の配列やオブジェクトと同じようなものと覚えておいてください。 chrome.tabs.query({/*条件を記述*/}, tabs => { }); 例えば、上記のプログラムを実行した場合、/*条件を記述*/ のところに記述した条件に合うタブの情報が格納された NodeList が、tabsに代入されます。 tabs => {} という書き方を初めて見る方もいらっしゃるかもしれませんが、function (tabs) {}と同じです。これは「アロー関数」と呼ばれるものです。気になる方は調べてみてください。 それでは、肝心の条件式について見ていきます。 条件式はオブジェクト形式で、{項目1: true/false, 項目2: true/false, ...} のように書いていきます。 主な「項目」は以下のとおりです。 項目 説明 active ウインドウ中のアクティブなタブか currentWindow 現在のウインドウにあるタブか muted ミュートになっているタブか pinned 固定されているタブか lastFocusedWindow 最後にフォーカスしたウインドウにあるか その他の「項目」については、以下の公式サイトをご覧ください。 tabs[0].url で、条件に適したタブのうち1番目のURLを得ることができ、tabs[0].title で、条件に適したタブのうち1番目のタイトルを得ることができます。 この NodeList は「オブジェクト」の「配列」になっているため、配列の1番目(といっても配列の長さは1だけですが)であることを明確にする必要があります。 ここで、先程のコードを読み返してみましょう。 popup.js chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, tabs => { title.value = tabs[0].title; url.value = tabs[0].url; }); 条件の部分は、{ 'active': true, 'lastFocusedWindow': true }となっています。これは、「ウインドウの中のアクティブなタブ」かつ「最後に開いたウインドウの中にあるタブ」ということなので、「現在見ているタブ」を指していることが分かります。 ということで、JavaScript のコード全体は以下のようになります。 popup.js const title = document.getElementById("pageTitle"); const url = document.getElementById("pageURL"); chrome.tabs.query({ 'active': true, 'lastFocusedWindow': true }, tabs => { title.value = tabs[0].title; url.value = tabs[0].url; }) このコードを動かしてみると... いい感じですね! では、今回はこの辺にして、次回はメインの「コピー機能」と SNS への「シェア機能」を実装していきます。お楽しみに! 問題点と予告 実は、このコードにはまだ問題があります。この画像は、Chrome の拡張機能の管理画面のスクリーンショットです。 この画像からわかるように、権限が多すぎるのです。この程度のプログラムであれば、「閲覧履歴の読み取り」をしなくとも実装できますが、結果的にユーザーを不安にさせてしまうような権限になってしまいました。これについては、次々回で対処していきます! まとめ chrome.tabs.query でタブの検索が行える。 お疲れさまでした! なお、今回扱ったコードは、こちらから見られます。 【連載一覧】 環境構築 ポップアップ作成 仕様に沿って開発 (←今ココ) コピー機能実装 権限を減らす コンテキストメニューの作成 Webストアに公開
- 投稿日:2021-08-09T14:14:38+09:00
Chrome 拡張機能を作って公開しよう② 〜ポップアップの作成〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 拡張機能を作るシリーズ第2弾!今回は拡張機能のポップアップを作成していきます! これでかなり拡張機能っぽくなります。 ポップアップとは 拡張機能のアイコンをクリックした時に出てくるもののことです。ここから拡張機能の設定や操作を行うことができます。拡張機能を使ったことがあれば、一度は目にしたことがあるでしょう。 実は、このポップアップは、HTMLとCSSでできています! ということで、サクッと作っちゃいましょう! 今回作っていくもの 前回、「作るものはお楽しみ」と言っておきましたが、今回作っていく拡張機能が、ついに決まりました。 それは、「開いているページのタイトルとURLをコピーする拡張機能」です。 イメージはこんな感じです。(汚くてすみません)↓ では、早速作っていきましょう! 設定ファイルを作る ブラウザに、「これはこのような拡張機能ですよ」と教えてあげるための、設定ファイルを作っていきます。 拡張機能開発用のフォルダを作って、manifest.json を作成してください。その後、下のコードを記述してください。 manifest.json { "name": "タイトルとURLをコピー", "subheading": "ページのタイトルとURLを簡単にコピーできます。", "version": "1.0", "manifest_version": 3, "icons": { "16": "icon_16x16.png", "48": "icon_48x48.png", "128": "icon_128x128.png" }, "action": { "default_title": "タイトルとURLをコピー", "default_icon": "icon_48x48.png", "default_popup": "popup.html" } } では、このmanifest.jsonについて解説していきます。 名称 説明 name 拡張機能の名称 subheading 拡張機能の説明。ブラウザの「拡張機能の管理」ページなどに表示される。 version 拡張機能のバージョン。 1.0→1.0.1→1.0.2→...→1.1→...→2.0 のように新しくなっていく。アップデートのたびにバージョン番号を新しくしていく。 manifest_version この設定ファイル(manifest.json)の仕様のバージョン。ここでは最新の 3 を指定。 icons 拡張機能のアイコン。16x16、48x48、128x128の3種類の大きさを用意しなくてはならない。アイコンの画像は後ほど配布します。 action ポップアップに関する設定。default_titleで、拡張機能のアイコンにカーソルをホバーした時に表示されるテキストを指定。default_iconで、ブラウザのURLバーの右側に表示される、拡張機能のアイコンを指定。ここでは、先程のiconsで指定した48x48の画像を流用。default_popupで、ポップアップとして表示させるHTMLファイルを指定。 アイコン アイコンは下のリンクからダウンロードできます。 .zip形式ですので、解凍して拡張機能の開発用フォルダの一番上の階層に、3つの画像を配置してください。 ダウンロード 今回は、「ICOOON MONO」様からお借りしました。この場をお借りして御礼申し上げます。 本家様 なお、現在のフォルダの中身はこのようになっています。 HTML ファイルの作成 それでは、HTML ファイルを記述してポップアップを作っていきます。 開発用フォルダの一番上の階層に、popup.html を作成し、以下のコードを記述してください。 popup.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="popup.css"> </head> <body> <p class="hello">Hello, world</p> <script src="popup.js" defer></script> </body> </html> 同様に、popup.cssも作成し、以下のコードを記述してください。 popup.css body{ width: 400px; } .hello{ color: #333; font-size: 30px; } 最後に、popup.jsを作成し、以下のコードを記述してください。 popup.js setTimeout(() => { alert("Hello, world! (from js)"); }, 700); 現時点でのフォルダの中身はこのようになっています。 テストする ここまで、 設定ファイル(manifest.json) HTMLファイル(popup.html) CSSファイル(popup.css) JavaScriptファイル(popup.js) を作成してきました。 それでは、ちゃんと動作するかテストしてみましょう。 テストの手順は以下のとおりです。 Chrome で chrome://extensions を開く 画面右上の「デベロッパーモード」をオン 「パッケージ化されていない拡張機能を読み込む」 拡張機能の開発用フォルダを「開く」 このように、拡張機能がリストに追加されていれば成功です。 リストに追加されていることを確認できたら、URLバーの右側にある「パズルのピースのマーク」をクリックすると表示される拡張機能の一覧に、「タイトルとURLをコピー」という拡張機能が追加されているはずなので、クリックして開いてみてください。 このようなものが表示された後、「Hello, world! (from js)」というアラートが表示されたら完璧です! なお、拡張機能の一覧のピンのマークをクリックすると、URLバーの右側に固定されるので、今後テストをする際に便利です。 まとめ ポップアップ付き拡張機能の作成には、manifest.json、HTMLファイル、JavaScript ファイル、CSS ファイルが必要である。 manifest.jsonは拡張機能の情報を、ブラウザに伝えるためのファイルである。 HTML、CSS、JavaScript でポップアップを作成する。ここは、普通のWebアプリと変わらない。 Chromeの「デベロッパーモード」を使えば、拡張機能を簡単にテストできる。 いかがでしたか? 次回は、このポップアップを更に充実させていきます! なお、今回扱ったコードは、こちらから見られます。 【連載一覧】 環境構築 ポップアップ作成 (←今ココ) 仕様に沿って開発 コピー機能実装 権限を減らす コンテキストメニューの作成 Webストアに公開
- 投稿日:2021-08-09T14:14:11+09:00
Chrome 拡張機能を作って公開しよう① 〜環境構築〜
この記事は、先日ブログに投稿したものと同じ内容です。ぜひ、ブログの方もご覧ください。 今回からは、連載企画ということで Chrome の拡張機能を開発していきます。何回分に渡るかはわかりませんが、1回分のボリュームを抑えていくので、物足りなさを感じてしまう方もいらっしゃるかもしれませんが、お付き合いよろしくお願いします。 さて、今回は連載第1回目、ということで環境構築を行っていきます。といっても、普段からプログラミングをされている方は、やることがほぼないです!プログラミングをされていない方も、難易度はそこまで高くありませんので、身構えずに取り組んでみてください。 対象 HTML、JavaScript、CSS の基礎が分かる方 (DOM操作とか...) これらがわからないという方は、まずは Webアプリを作るところから始めてみましょう! Chrome 拡張機能開発ではこれら3つの言語が必須になります。 ※HTML、CSS はものによっては使わないかも 必要なもののインストール Google Chrome 当たり前ですが、Google Chrome が必要です。開発した拡張機能をテストするのに用います。 普段から Google Chrome を使われている方は、再度インストールする必要はありません。そのままで OK です。 エディタ プログラムを書いていくために必要なエディタをインストールします。これも、普段使われているもので結構です。 ただし、WIndows 標準の「メモ帳」を使われている方は、特にこだわりがないのであれば、今は無料で高機能なものがたくさんあるのでこれを機に是非インストールしましょう。 おすすめは「Visual Studio Code」です。 ダウンロードページ インストールしたら、デフォルトでは画面が英語なので日本語化します。以下のサイトを参考にすればすぐにできます。 Visual Studio Codeを日本語表示にする手順!変更方法も解説 (.NET Column) これで環境構築は終了です!あっという間でしたね! 誰でも気軽に開発を始められるところが、Chrome 拡張機能開発の良い点の一つだと思います。 公開するなら... 開発したChrome 拡張機能をウェブストアで公開するには、上のものに加えて以下のものが必要になります。 - Google アカウント (できれば開発用のものを作ったほうが良い) - 5ドル (ウェブストアにアカウントを登録する際に、1度だけ登録料を支払う必要がある) - クレジットカード or デビットカード (どうしても用意できない場合は、本人確認を済ませたLINEプリペイドカードでも可) 開発の流れ 必要なファイルを用意する コードを書く テストする 2.3.を繰り返し、バグをなくしていく 公開! (これは任意) では、今回はこれくらいにします。次回からいよいよ本格的に開発を行っていきます! 【連載一覧】 環境構築 (←今ココ) ポップアップ作成 仕様に沿って開発 コピー機能実装 権限を減らす コンテキストメニューの作成 Webストアに公開
- 投稿日:2021-08-09T13:37:59+09:00
[初心者向け] BootstrapのCarouselを暗くして背景として使う
はじめに 右も左もわからない未経験エンジニアが書く初めての投稿です。 今後も膨大な勉強量の中で、気になったことや発見などを書いていこうと思います。 間違いや記事を見て良かったことなど 気軽にコメントを残して頂けると活力に繋がりますのでよろしくおねがいします 今回は、前回投稿した 「BootstrapのCarouselの上にコンテンツを重ねる」 に追加する形でお送りしたいと思います。 今回やること 前回は好みのコンテンツをcarousel上に載せるところまでやりました♪ 今回は、carouselのみを暗くし、前面のコンテンツを引き立たせる効果を付けます 正直、とても簡単なので自身のアイディアを膨らませる一手になってもらえれば幸いです 開発環境 Rails 6.1.3.2 ruby 2.5.1 Docker version 20.10.6 Bootstarp v4.5 前回のコード javascript/stylesheets/application.scss .ps-relative { position: relative; } .ps-absolute { position: absolute; top: 0; left: 0; } html.erb <div class="ps-relative"> <div id="carouselExampleIndicators" class="carousel slide" data-ride="carousel"> <ol class="carousel-indicators"> <li data-target="#carouselExampleIndicators" data-slide-to="0" class="active"></li> <li data-target="#carouselExampleIndicators" data-slide-to="1"></li> <li data-target="#carouselExampleIndicators" data-slide-to="2"></li> </ol> <div class="carousel-inner" style="height: 600px;"> <div class="carousel-item active"> <svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: First slide"><title>Placeholder</title><rect width="100%" height="100%" fill="#777"/><text x="50%" y="50%" fill="#555" dy=".3em">First slide</text></svg> <div class="carousel-caption d-none d-md-block"> <h5>...</h5> <p>...</p> </div> </div> <div class="carousel-item"> <svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: Second slide"><title>Placeholder</title><rect width="100%" height="100%" fill="#666"/><text x="50%" y="50%" fill="#444" dy=".3em">Second slide</text></svg> <div class="carousel-caption d-none d-md-block"> <h5>...</h5> <p>...</p> </div> </div> <div class="carousel-item"> <svg class="bd-placeholder-img bd-placeholder-img-lg d-block w-100" width="800" height="400" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid slice" focusable="false" role="img" aria-label="Placeholder: Third slide"><title>Placeholder</title><rect width="100%" height="100%" fill="#555"/><text x="50%" y="50%" fill="#333" dy=".3em">Third slide</text></svg> <div class="carousel-caption d-none d-md-block"> <h5>...</h5> <p>...</p> </div> </div> </div> <a class="carousel-control-prev" href="#carouselExampleIndicators" role="button" data-slide="prev"> <span class="carousel-control-prev-icon" aria-hidden="true"></span> <span class="sr-only">Previous</span> </a> <a class="carousel-control-next" href="#carouselExampleIndicators" role="button" data-slide="next"> <span class="carousel-control-next-icon" aria-hidden="true"></span> <span class="sr-only">Next</span> </a> </div> <div class="ps-absolute" style="top: 45%; left: 42%;"> <h1>Hello World</h1> </div> </div> 本題 まずはわかりやすいように写真を入れます 今回は10行目のsvgタグのみをimgタグに変え、写真までのパスを記述します html.erb <div class="carousel-inner"> <div class="carousel-item active"> <img src="/assets/好きな写真のファイル名"> <!-- svgファイルから変更 --> <div class="carousel-caption d-none d-md-block"> <h5>...</h5> <p>...</p> </div> </div> 本当にプログラミングを始めた当初、imgタグの記述法に時間を取られた記憶があります笑 パスを記述しているので、何も考えずに書けば /assets/images/好きな写真のファイル名 となるのですが imagesは邪魔です笑 ここで覚えておきましょう! これで写真が表示されるようになりました しかし、このままだと画像が明るすぎて載っているコンテンツが目立ちません ここで画像を暗くするために javascript/stylesheets/application.scss .ps-relative { position: relative; } .ps-absolute { position: absolute; top: 0; left: 0; } // 以下を追加 .filter { background: #000; } .filter-img { display: block; opacity: .2; //どれくらい暗くするかを設定する数値。低いほど暗くなるようにしてます } .w-100 { width: 100%; } html.erb <div id="carouselExampleIndicators" class="carousel slide carousel-fade" style="z-index: 0;" data-ride="carousel" data-interval="2000" data-pause="false"> <div class="carousel-inner" style="height: 600px;"> <div class="carousel-item active"> <!-- 変更点 --> <div class="filter"> <img src="/assets/bike.jpg" class="d-block w-100 filter-img" alt="..."> </div> <!-- ここまで --> <div class="carousel-caption d-none d-md-block"> ・ <!-- 省略 --> ・ <div class="ps-absolute" style="top: 45%; left: 42%;"> <h1 style="color: white;">Hello World</h1> <!-- 写真が暗いとわからなくなるので文字を白くしました --> </div> </div> </div> これで背景としてcarouselを使用することができます♪ 最後に 勉強を始めたばかりで知識もなく、拙い文章ですがアウトプットすることで頭の中を整理しつつ、どなたかのお役に立てれば良いなと思い投稿させて頂きました。 最後まで見て頂きありがとうございました!