はじめに
この度Font Awesomeのアイコンピッカーを作成しGithubで公開しました。
その際の少しおもしろい使用方法を使ったので、こちらに残しておきたいと思います。
Font Awesomeのアイコンピッカーは「itsjavi/fontawesome-iconpicker」が有名どこのようですが、2018年に更新が止まり、使用を試みましたがうまく動作しないことが確認できました。
itsjavi/fontawesome-iconpicker
こちらを改良して使用するてもあったのですが、今回の目的としては、npmが使いたいわけでもなく、SVGデータが欲しいわけでも、フレームワーク上やCMSに乗せるわけでもないので、大した機能が必要なわけではなく。
そもそもFont Awesome4までしか対応してなく、javascript内にあるアイコンリストから表示を作っているようで、これを更新して使うのは一苦労になりそうな感じです。
2022年になり、そろそろFont Awesome6もBeta版から正式バージョンになりそうです。
正式バージョンがリリースされました。
自分で作るしかないかなと思い立ちライブラリ風にまとめてみました。
使用したい&使い方を知りたい人は、直下のダウンロードから使ってください。
Qiita(本記事)では後で思い出せるように参考にした文献を貼りながらCSSからアイコンリストを作る技術解説をしたいと思います。
Font Awesome6からWordPress用のアイコンピッカープラグインが公式で配布されていますので、WordPressで使いたい方は、公式プラグインのほうが出来がいいですのでお勧めです。
Font Awesome Wordpressプラグイン
ダウンロード&マニュアル
こちらからどうぞ
手段(本編)
Font AwesomeにはCSS版と、JavaScript版があります。
どちらのファイルから一覧を作ってもよかったのですが、
CSSから作成する方が面白そうだったのでCSSから作ることにしました。
動作確認環境・依存関係
Chrome バージョン: 96.0.4664.110(Official Build) (64 ビット)
bootstrap 5.1.3
Font Awesome Free 6.0.0-beta3
document.styleSheets
JavaScriptからCSSファイルの中身を扱うには、document.styleSheetsを使用します。
document.styleSheets[n].cssRulesの中にCSSファイルの内容がCSSStyleRuleの形式で格納されています。
CSSStyleRule内の定義は、Element.styleでの書き方と同じです。
const targetCssName = "ファイル名.css";
let targetSheet = styleSheets.filter((styleSheet) => styleSheet.href !== null && styleSheet.href.endsWith(targetCssName))[0];
こんな感じで、CSSを1つに絞ります。
CORS問題の回避
CORSで指定した(CDNなど)CSSファイルのrulesはGoogleChromeでは参照できないようです。
Font AwesomeのCSSファイルをダウンロードしてきて使用すれば解決できます。
それだと使いにくいので、CSSをAjaxで持ってきてDOMにコピー
function loadCSSCors(stylesheet_uri) {
var _xhr = globalThis.XMLHttpRequest;
var has_cred = false;
try {has_cred = _xhr && ('withCredentials' in (new _xhr()));} catch(e) {}
if (!has_cred) {
console.error('CORS not supported');
return;
}
var xhr = new _xhr();
xhr.open('GET', stylesheet_uri);
xhr.onload = function() {
xhr.onload = xhr.onerror = null;
if (xhr.status < 200 || xhr.status >= 300) {
console.error('style failed to load: ' + stylesheet_uri);
} else {
var style_tag = document.createElement('style');
style_tag.appendChild(document.createTextNode(xhr.responseText));
document.head.appendChild(style_tag);
const targetSheet = document.styleSheets[document.styleSheets.length-1];
targetSheet.disabled = true;
draw(targetSheet);
}
};
xhr.onerror = function() {
xhr.onload = xhr.onerror = null;
console.error('XHR CORS CSS fail:' + styleURI);
};
xhr.send();
}
一度読み込んだCSSをもう一度わざわざ読み込んでいるので、無駄ではありますが……
新しくCSSが読み込まれると、一番最後に追加されるので
const targetSheet = document.styleSheets[document.styleSheets.length-1];
と書けば、StyleSheetが特定できます。
WEBフォントの読み込みエラーが出るのと、CSSが2重適用されてしまうので、無効にしておきます。
targetSheet.disabled = true;
cssRulesの中から、Font Awesomeのフォント情報だけを取り出す
Font Awesomeのフォントを指定している部分は
.class::before {
content: "文字コード"
}
になっているので、
const members = Array.from(targetSheet.rules).filter(v => v instanceof CSSStyleRule && v.style.content !== "");
content
があるルールだけを取り出します。
CSSStyleRule
ではないオブジェクトも含まれているので、エラーしないように取り除きます。
画面に書き出す
function draw(targetSheet){
const members = Array.from(targetSheet.rules).filter(v => v instanceof CSSStyleRule && v.style.content !== "");
const drawEl = document.body;
let zFlag = false;
members.forEach(function(v,k) {
if(members[k-1] !== void 0 && members[k-1].style.content == v.style.content) return;
const iconName = v.selectorText.replace("::before", "").replace(".","");
const i = document.createElement("i");
i.classList.add(zFlag ? 'fa-brands' : 'fa-solid',iconName.split(', .')[0]);
const btn = document.createElement('button');
btn.classList.add('btn','btn-outline-secondary', 'm-1');
btn.setAttribute('data-fapc-icon-name',iconName);
btn.setAttribute('data-fapc-icon-type',zFlag ? 'fa-brands' : 'fa-solid');
btn.style.width = "3em";
btn.style.height = "3em";
drawEl .appendChild(btn).appendChild(i);
if(iconName == "fa-z"){
zFlag = true;
}
});
}
Font Awesome6は英文字1字の「A」fa-a
~「Z」fa-z
までのアイコンが用意されていて、brandアイコンとの境目がちょうど、fa-z
なので、fa-z
が来たら次からbrandアイコン用のclassに切り替えています。
イベントを作ったりする
ボタンにイベントを付けたり、受け取るinputを用意したりする。
この辺は、WEB上にたくさん知見があると思います。
宗教上の理由で記述方法が決まっている方もいると思います。
なので、今回は省略します。
完成
HTMLなどを書いて完成
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/@fortawesome/fontawesome-free@6.0.0-beta3/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js" integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p" crossorigin="anonymous"></script>
<script type="text/javascript">
function init(){
const targetCssName = "all.min.css";
let targetSheet = Array.from(document.styleSheets).filter((styleSheet) => styleSheet.href !== null && styleSheet.href.endsWith(targetCssName))[0];
if(targetSheet !== void 0 && targetSheet.href && !targetSheet.href.startsWith(window.location.origin)){
loadCSSCors(targetSheet.href);
}else{
draw(targetSheet);
}
}
function draw(targetSheet){
const members = Array.from(targetSheet.rules).filter(v => v instanceof CSSStyleRule && v.style.content !== "");
const drawEl = document.body;
let zFlag = false;
members.forEach(function(v,k) {
if(members[k-1] !== void 0 && members[k-1].style.content == v.style.content) return;
const iconName = v.selectorText.replace("::before", "").replace(".","");
const i = document.createElement("i");
i.classList.add(zFlag ? 'fa-brands' : 'fa-solid',iconName.split(', .')[0]);
const btn = document.createElement('button');
btn.classList.add('btn','btn-outline-secondary', 'm-1');
btn.setAttribute('data-fapc-icon-name',iconName);
btn.setAttribute('data-fapc-icon-type',zFlag ? 'fa-brands' : 'fa-solid');
btn.style.width = "3em";
btn.style.height = "3em";
drawEl .appendChild(btn).appendChild(i);
if(iconName == "fa-z"){
zFlag = true;
}
});
}
function loadCSSCors(stylesheet_uri) {
var _xhr = globalThis.XMLHttpRequest;
var has_cred = false;
try {has_cred = _xhr && ('withCredentials' in (new _xhr()));} catch(e) {}
if (!has_cred) {
console.error('CORS not supported');
return;
}
var xhr = new _xhr();
xhr.open('GET', stylesheet_uri);
xhr.onload = function() {
xhr.onload = xhr.onerror = null;
if (xhr.status < 200 || xhr.status >= 300) {
console.error('style failed to load: ' + stylesheet_uri);
} else {
var style_tag = document.createElement('style');
style_tag.appendChild(document.createTextNode(xhr.responseText));
document.head.appendChild(style_tag);
const targetSheet = document.styleSheets[document.styleSheets.length-1];
targetSheet.disabled = true;
draw(targetSheet);
}
};
xhr.onerror = function() {
xhr.onload = xhr.onerror = null;
console.error('XHR CORS CSS fail:' + styleURI);
};
xhr.send();
}
document.addEventListener('DOMContentLoaded', init);
</script>
</head>
<body>
</body>
</html>
CSSからアイコンのリストを無事に作ることが出来ました。
たくさんありすぎる……
公開しているライブラリでは
- アイコン検索(FontAwesomeでの英語名のみ)
- 除外機能
- inputタグへの値渡し機能
- ポップアップ(poper.js on bootstrap)
などに対応しています。
ダウンロード&マニュアル(再掲)
MITライセンスにて公開中です。
よかったら使ってみてください。
おわり(感想)
CSSの要素を解析して、リストを作る方法を検証しました。
CORS問題などがなければかなり簡単にできることが分かりました。
あと、Array.filterって便利だなと最近多用しております。(いまさら)
今回の使おうと思っていた用途ではSolidのアイコンのみでいいのですが、
Font Awesome 6から、Regularのアイコンも一部無料で使えるようになっていますが、この方法だと対応できていません。
何かいい方法があればコメント欄などで教えていただければ嬉しいです。