MBPを買ってからというもの自分でも見えちゃうので、Retinaディスプレイでの自分のブログの画像の見え方、なんとかしたいな〜と思っておりました。
ご存知の通り、retina用に縦横2倍サイズの画像を用意して対策するのが良いようです。
便利なJqueryプラグインには及びませんが、いつものjQuery学習という事でタイトル周りとテンプレートのイラストとiconだけでも自分でなんとかしてみることにしました。
wordpressで投稿した画像などは未対応です。これはどうしようかまだ基本方針がかたまりませんも。
iPhoneはどうでもよくてよ!
別サイトを用意してないのでこのブログは大きいディスプレイとモバイルと共用です。
iPhoneに関しては基本、横幅1000pxの土台を縮めて表示してる訳なので、おおむね奇麗!
なるべく本文はiPhoneでも(ダブルタップで拡大されるブロックの幅調整で)見やすいようにしているつもり…という逃避w
へっぽこスクリプトも相まって画像で重くなるのもなんですし、
retinaディスプレイを切り分けて、なおかつiPhoneは除外しようと思います。
|
$(function(){ var retinaSwitch = window.devicePixelRatio; if(retinaSwitch > 1) { //retina var agent = navigator.userAgent; if (agent.search(/iPhone/) == -1) { //not iphone //retina用の処理 } } }); |
retinaディスプレイか否かはwindow.devicePixelRatioで判定。retinaは1よりも大きい数字を返すだす。
そんで、ifを入れ子にしてiPhoneかどうかを判定。
.searchメソッドは引数の正規表現が見つからない場合-1を返します。
よってこの場合、-1だったらiPhoneじゃないもんね!ということになるので、ここにiPhone以外のretinaディスプレイの場合に、画像を差し替えるスクリプトを書いてゆくだす。
差し替える画像は全部じゃないのよ…
とりあえず差し替えたい画像は、このブログで言うと
- ブログのタイトルロゴ(img)
- タイトルバックのイラスト(background-image)
- 記事のタイトル h1,h2(background-image)
- 小見出し h5(background-image)
- リンク部分の背景(background-image)
- Twitter用のオリジナルひよこアイコン(img)
ありゃー、imgタグによる画像挿入とcssのbackground画像と2パターンあります。うっ。
一括では行かないので先ず背景画像から何とかしてみたいと思います。
画像は従来表示していた物に加え、縦横それぞれ倍の大きさにした画像をめんどくさいけど用意しました。元データを残しといて良かったです。残ってないのもありましたが…。
2倍画像の名前の末尾に_rtiとか安易な文字を足して、replaceと正規表現でurlを書き換えます。@2とかの方がかっこいいかも。ふん。
なにからとっかかったらいいのか謎です。とりあえず背景画像(background-image)のurlを書き換える関数から作ってみることに。
$().eachはなんとか出来るようになったので、今回は苦手意識があったforで繰り返してみます。こちらの方がjavascriptネィティブで早いんだとか。
|
function retina (chunBacks) { for (var i=0, n=chunBacks.length; i<n; i++) { //background-image url 取得 var retinaBack = chunBacks.eq(i).css('backgroundImage'); //url置き換え var retinaUrl = retinaBack.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/g, "$1_rti$2"); //タグにcss書き込み chunBacks.eq(i).css({ backgroundImage:retinaUrl, backgroundSize:'contain' }); } return chunBacks; //セレクター再取得 } |
.lengthをその都度取りにいかない
ハイライトの所、i=0と同時に変数に突っ込んでおくと最初の1度で済み、より早いそうです。
一見、forやeachで繰り返し処理をしなくてもいいような気がするんですが、実際ブラウザでも各要素にstyleが書き込まれるけどエラーがでまふ。
replaceのところがundefinedになっちゃう。
複数あるh5だと最初のh5に対してだけのcssの’backgroundImage’のurlが変数に入り、次ぎからのh5のurlを拾えないからだと思うんですが、さて。
最初のeq(0)だけ拾って一括でbackgroundを指定できないの?どうなの?
と思ったのですが、例えばh5が無いページとか該当する要素が無いとやっぱりundefinedになっちゃい汎用性がありませぬ。
この関数の引数には色々突っ込むので、今回はおとなしくループをしておいた方が良いようです。
本題に戻って自作関数の引数(chunBacks)に入れているのは’$(セレクタ).トラバース’の要素を選択の部分。
retina振り分けのifの中で
とかすると呼び出されるという寸法です。
なんでもかんでも変数に入れたい病
引数に入れる要素’$(セレクタ).トラバース’を変数に入れて置きたくなりました。
何度も同じ要素を探しに行くことになるし、特に’dl.ref a’なんかclassからだし、効率が悪いような気がします。
|
var $main = $('#main'), wrapp = $('#wrapper'), sTit = $main.find('h1,h2'), ch5 = $main.find('h5'), refs = $main.find('dl.ref a'); |
にしたい。したいよー。
|
retina(wrapp); retina(sTit); retina(ch5); retina(refs); |
そして、backgroundの数だけこのretina関数を呼び出すのも少々うざいです。
で、今回、考えた末にこれらの変数を配列に入れて$.eachで繰り返してみました。
なんで考えたかというと、変数を$(‘#hoge,#fuga’)の様にコレとコレとする書き方が分からなかったのです(´・ω・`)。
|
var retinaSwitch = window.devicePixelRatio; if(retinaSwitch > 1) { var agent = navigator.userAgent; if (agent.search(/iPhone/) == -1) { //not iphone var mosBacks = [wrapp,sTit,ch5,ref];//background $.each(mosBacks, function(index, val) { retina(val); }); } |
配列を扱うにはこっちの’$.each’ってことで使ってみました。コールバックの引数にindexと値が受け渡されるので、直接valで値が渡せて便利です。
でもイケますがvalの方がスッキリ。
たしかに4回書かなくても良くなったけど、タイプ数は大して変わらない気もします。
いや、きっと対象が増えたら便利に違いない。違いないったら。
悔しいので利便性を追求してみる
配列の書き方をpushで追加方式に変えてみる。
|
var mosBacks = [];//background mosBacks.push(wrapp); mosBacks.push(sTit); mosBacks.push(ch5); mosBacks.push(refs); |
新しいbackgroundが増えたら後ろにどんどん追加していけばいいし、見やすい気がする…
しかし、これだと要素を変数に入れてから配列に入れているので
↓ここにも変数を追加しなくちゃなりません。
|
var $main = $('#main'), wrapp = $('#wrapper'), //以下略… |
めんどくさい…
オブジェクト(連想配列)にしてしまえば、個々に変数に入れなくてもいいような…一度の追記で済むんじゃ?
コンストラクタとかインスタンスとかまだ手が届かないので今回は追求しない方向で…
|
var mosBacks = new Object();//background mosBacks.wrapp = $('#wrapper'); mosBacks.sTit = $main.find('h1,h2'); mosBacks.ch5 = ch5; //使い回してる変数 mosBacks.refs = $main.find('dl.ref a'); |
お名前付き関数を作りましたが、オブジェクトにすると$.eachのコールバックの引数、プロパティ名(key)とプロパティ値(value)が使えそうなのでeach内に書き直してみます。
|
var retinaSwitch = window.devicePixelRatio; if(retinaSwitch > 1) { var agent = navigator.userAgent; if (agent.search(/iPhone/) == -1) { //not iphone $.each(mosBacks, function(key, val) { for (var i=0, n=val.length; i<n; i++) { var retinaBack = val.eq(i).css('backgroundImage'); var retinaUrl = retinaBack.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/g, "$1_rti$2"); val.eq(i).css({backgroundImage:retinaUrl}); if(key != "refs"){ //refsじゃないのよ val.eq(i).css({backgroundSize:'contain'}); }else{ //refsなのよ val.eq(i).css({backgroundSize:'40px'}); } } }); |
firebagで確認するとfor文ブロックの中のvalにもオブジェクトmosBacksの値である$(‘セレクタ’)がちゃんと渡されてます。
10行目、後で上書きするしかないと思っていたところもif文でkey(プロパティ名)を比較して分岐が出来るようになりましたラッキー。(?)
よしこれで行ってみよう。
imgはどうなの
background-imageの時と大体同じ、srcを操作するだけですな。
|
var mosImg = new Object(); //image mosImg["header"] = $('#header img'); mosImg["my_twitter"] = $('#my_twitter>h3 img'); mosImg["foot_twitter"] = $('#twitter_img>img'); for (var mkey in mosImg) { var mos = mosImg[mkey]; var rtiImg = mos.attr('src'); var rtiSrc = rtiImg.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/, "$1_rti$2"); mos.attr('src',rtiSrc); } |
今回はオブジェクトにブラケット表記[ ]でプロパティを追加してみました。ドッド演算子と書き方が違うだけで同じ事をしているだけなんだけど、書いてみるとなんとなく連想配列で値を取り出す方法も身に付くような?気がします。
ブラケット表記では中身を文字列として扱う。
変数も入れられる。
ちなみに6行目、for の引数の中で mkeyと変数を宣言しているので””で囲って文字列にするとundefined。
そして今度は$.eachではなく、練習の為にfor in文で繰り返してみました。
for in文はオブジェクトに含まれるプロパティの数だけ繰り返し処理をする。
順不同だけど今回の場合は全然問題なし。
backgroundの時の様に繰り返し処理をしなくても大丈夫。今のところimageは親要素に対してそれぞれ一個づつしかないしー、
複数個ある場合は繰り返さなくてはならんのであとで書き直しが生じるけど、今のところ大丈夫だしー。(ちょっと頭が追いついていません、すみません)
しかし!問題発生…(追記です。)全然大丈夫じゃなかった。がっくし。
該当するimg要素がないとundefinedになっちゃうのよ…
//2015/3 追記しました。
オブジェクトに入れたimg要素が全て揃っている時は上記の方法で問題ないのですが、ハイライト部分のimgを使わないページで、エラーがでます。
|
var mosImg = new Object(); //image mosImg["header"] = $('#header img'); mosImg["my_twitter"] = $('#my_twitter>h3 img'); mosImg["foot_twitter"] = $('#twitter_img>img'); |
タイプエラーとな。
|
TypeError: undefined is not an object (evaluating 'rtiImg.replace') |
そもそもhtmlに要素が存在しないので、srcを取得をぶっ込んだ変数がundefined。変数を利用する文字列置き換えのスクリプトの部分が評価されず、後続の存在するimgのみならず後に読み込まれるスクリプトまでも止まってしまいます。うう。
img要素の有無を判定をして、continue的な事ができればいいんだけど…
for inだとどうやるのか分からないので、またjqueryの$.eachにお世話になります。…いいんだ学習だから。ごめん。
$.eachで処理を飛ばすには、こちらを参考に。
- 参考サイト:
- jQuery::ループ内の continue / break [Tipsというかメモ]
ifの条件にしたい要素の有無判定をこちらを参考にさせてもらいました。
- 参考サイト:
- jQueryによる要素の存在チェックまとめ: 小粋空間
書いてみる。
|
var mosImg = new Object(); mosImg["header"] = $('#header img'); mosImg["my_twitter"] = $('#my_twitter>h3 img'); mosImg["foot_twitter"] = $('#twitter_img>img'); $.each(mosImg, function(key, val) { if (val[0]) { var rtiImg = this.attr('src'); var rtiSrc = rtiImg.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/, "$1_rti$2"); this.attr('src', rtiSrc); } else { return true; } }); |
6行目、val(値)にはそのままセレクターを突っ込んであり、今の所セレクターに対応するimg要素は1個しかないのでimg要素が存在したら0番目の要素となります。
ifの条件”val[0]”でセレクターの0番目がゲットできたら処理。img要素が存在しなかったらelseとなるので、 return true(continue)で処理が飛ばされます。
しかし、やっぱり2個以上セレクターに対する要素が存在したらelseになるので、その場合はまた内部でforなりしなくてはならくなるかと。
余談だけど、なんでbackgroundの方ではh5など要素が無くてもエラーにならなかったのか疑問だったのだすが、eachの中のfor文の条件式によって処理が飛ばされておったのですな。
htmlに要素がない場合eachには’head{}’と空のオブジェクトが返ってきて、それに対して第2引数のfunctionが実行されるんだけど、中のfor文は条件式によってlengthが0の場合は実行されない。functionへの返り値は恐らくundefinedになるのかな?
eachの第2引数へfalse以外のfalse的な値が返ると要素の処理がスキップされるので結果オーライだったと…そ、そうだったのか。
eachに書き換えても存在確認の条件式が無いとタイプエラーになります。
「eachに書き換えたら、もしかしてこのundefainedスキップされるんじゃないの?」というイージーな思考が沸き起こりましたが、
このタイプエラーのundefinedは中の変数に対してであって、eachの第2引数のfunctionにundefinedが返ってる訳ではないのに早く気づくべきものでありました。(やってみた)
試行錯誤の末、全体はこんな感じになりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
$(function(){ var $main = $('#main'); var mosBacks = new Object();//background mosBacks.wrapp = $('#wrapper'); mosBacks.sTit = $main.find('h1,h2'); mosBacks.ch5 = ch5; //変数に代入済み mosBacks.refs = $main.find('dl.ref a'); var retinaSwitch = window.devicePixelRatio; if (retinaSwitch > 1) { var agent = navigator.userAgent; if (agent.search(/iPhone/) == -1) {//not iphone $.each(mosBacks, function(key, val) { for (var i = 0, n = val.length; i < n; i++) { var retinaBack = val.eq(i).css('backgroundImage'); var retinaUrl = retinaBack.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/g, "$1_rti$2"); val.eq(i).css({backgroundImage : retinaUrl}); if (key != "refs") { val.eq(i).css({backgroundSize :'contain'}); } else { val.eq(i).css({backgroundSize : '40px'}); } } }); var mosImg = new Object();//image mosImg["header"] = $('#header img'); mosImg["my_twitter"] = $('#my_twitter>h3 img'); mosImg["foot_twitter"] = $('#twitter_img>img'); $.each(mosImg, function(key, val) { if (val[0]) { var rtiImg = this.attr('src'); var rtiSrc = rtiImg.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/, "$1_rti$2"); this.attr('src', rtiSrc); } else { return true; } }); } else {//iphone用になにか } } }); |
// 追記終了
iPhoneにもなんかしたくなった時は、elseの中に書いてやります。
background-imageのためのオブジェクトがifの外に出てるのは、elseでiPhoneの処理をしようと思ったときにスコープってんですか?届かないんですね。関数の中だけなのかと思っておりました。
image用もiPhone用の処理をするときにはお外に出してやらないとなりませんな。
imgにはwidthいるよね…取得できないよね…(力尽きた)
画像のサイズは2倍なので、表示サイズを指定しなくてはなりません。
background-imageだとbackground-size:containなど便利なプロパティがあるのですが、imgはそうはいかんのです。
スクリプトでどうにかしようと思ったのですが、cssに指定してしまったほうが精神的に良いという結論に落ち着きました。
|
function retinaImg (chunImg) { var rtiImg = chunImg.attr('src'); var rtiSrc = rtiImg.replace(/(images\/[0-9a-zA-Z_-]+)(\.png|\.jpg)/, "$1_rti$2"); var rtiSize = new Image(); rtiSize.src = rtiSrc; var rwidth = rtiSize.width; chunImg.attr('src',rtiSrc).css({width:rwidth/2, height:'auto'}); } |
Firefoxではうまく行くのだけど、safariで画像サイズが読み込みのタイミングでか取得できずページ遷移では2回に1回くらいcssの指定が0pxになってしまう。
ブラウザボタンからのリロードでもサイズが0pxに。もがっ。
srcには生のパスを突っ込まないとダメなのかもしれません。new Imageの引数にサイズも指定できますが、それらをやろうとするとcssに記述するのと同じくらいの手間になり便利さは薄い訳で…やーめた!( ´・ω・)。
やってみて
実際に当wordpressで使ってまする。へんになってたらごめんなさい。指摘してやってください。safariとfirefoxでしか見てません(・ω・;)。
今回はjavascript成分が多めの勉強になりましたが、関数リテラルとかオブジェクトオブジェクトとかコンストラクタとかインスタンスとかなにそれおいしいのワカメ?的なことのほんのとっかかりが出来たのでよかったです。
javascriptは難しいなぁ。jQueryがあってよかったよぅ。