.oO なっから備忘録 Oo.
ホームページを作成したときの備忘録です。ここの落書き人のメモリーが揮発性ですのでメモっておきます。
※当該落書き人の備忘録ですのでHTML,CSS,JavaScriptの基礎的な説明は省いてあります。
逐次型プログラマが嵌まる非同期という落とし穴
逐次型プログラミングが染み付いたプログラマがハマるJavaScriptの非同期という落とし穴!!
これ、落書き人本人の話です。プログラム・ソース・コードの記述順に実行されるという潜入概念を捨てないとJavaScript記述の非同期の動作が理解できずに不具合を招きます。
特にハマるのがコールバックで記述する無名関数で記述した場合です。ここで記述した実行コードはコールバック時点で実行されるもので、コールバック記述の次に記述したコードが次に実行される訳でもなく、thisも引き継がれていません。この感覚が逐次型しか経験のないプログラマには理解しずらい部分です。非同期という感覚が身につかないとJavaScriptの理解ができません。
ECMAscript 2015(ES6)でますます記号のお化けみたいな記述がでてきて頭の切り替えに苦労しています。
なお非同期の素晴らしさはnode.jsで実感できます。逐次型プログラミングでより多くのトランザクション処理を行うためにはスレッド(又はタスク)で多重化して処理性能を上げるのが常識でした。非同期のnode.jsはシングルスレッドで有りながら処理能力が素晴らしい。今はnode.jsで非同期プログラミングに嵌っております。
SPA(single-page application)とCMS(Content Management System)手法
SPAですが「なっからHP」は文書としては単一なHTMLページです。厳密にはiframeを埋め込んでいますがそれは機能的に別物なページです。
SPAにした理由はHTMLページを増やすと整理整頓が苦手な落書き人にとってはトッチカらってしまうのが理由です。
なお、単一ページでコンテンツが増えると一文書として大きくなり見通しが悪くなります。そこで同種のコンテンツであるYouTubeはJSON(JavaScript Object Notation)で整理しています。その整理方法ですが、文書作成が楽な某グループウェア・ソフトを利用しています。そのソフトからJSONデータとして書き出して、ウェブ・サーバーにアップロードしています。
JSONでデータ化しているコンテンツは以下の通りです。
・なっからHPの手ぶちコンテンツ部分
・ちゅんラヂの選局データ
SPA(single-page application)内コンテンツ遷移とHistory Back/Forwardの関係
SPAで構成されたページ内の遷移ではスムーススクロールや表示・非表示で制御した場合に、この遷移をdocument.historyに記録させないと、ブラウザの前ページ・次ページが遷移通りに機能しません。利用者からするとブラウザで前ページで戻りたい場合に「あれ?」という事になります。
このSPAのページ内遷移をHistory Back/Forwardに上手く関連付けできるのがhistory APIです。なお忘れない為のメモですので、詳細は事例のソースコードを参照してください。
(1).ページ内リンクをスムーススクロール遷移させている場合
ハッシュリンクでスムース・スクロールさせる場合には、その関数でreturn falseと書くのが一般的です。これはスムーススクロールした後にハッシュリンク本来の動きを抑止するためです。この短所はhistoryに記録されません。
そこでhistory.pushState(state,null,URL)にてhistoryに遷移を記録することで対応できます。History Backでもスムーススクロールをさせる為には、window.addEventListener('popstate',function(e){},false);でキャッチします。
なお、historyに記録させるために、スムールスクロール後にlocation.hrefに代入する例を見かけますが、これは本来の遷移も加わる為にお勧めできません。というか本来の遷移が起きても良ければ、スムーススクロールさせる関数をreturn trueで抜ければ済む話です。
事例1:ちゅんラヂの説明ページ
事例2:株式会社ナックのホームページ
※依頼されて作成したサイトで、SPA内ナビ方法が少し凝っています。
(2).ブロックの表示・非表示でコンテンツを切り替えている場合
history.pushState(state,null)でコンテンツが切り替えられた時に、そのブロクのIDなどを記録しておきます。
そして window.addEventListener('popstate',function(e){},false); でブラウザの前ページ・次ページが行われたイベントをキャッチして、stateに記録しておいたブロックのIDを取得して、対応するコンテンツに切り替えます。
事例:なっからのトップページ
※ドロワー型切り替えでやりすぎていて鬱陶しいですが、お遊びサイトなので愛嬌。
(3).history.pushStateの引数について
引数1であるstateには任意のオブジェクトがセットできます。
この利用場面ですが、ウェブアプリケーションはhistory backに対応していな物が見受けられます。アプリケーションとして一つ前の状態を保持したり再現するのが難しいので気持ちはわかります。その状態保持にstateが使えます。
引数2であるtitleはブラウザが対応していない様ですので、現状はnullにしておきましょう。
引数3はURLです。ブラウザのアドレス欄に遷移の状況が表示できます。この値とするかしないかですが、その遷移のステップをお気に入りなどに登録されて、いきなりそのステップに遷移できるのなら、そのURLをセットして、いきなり遷移できないのであればnullをセットするのがお薦めです。
なおセットするURLですが、擬似的なパスを指定した場合には、その擬似的なパスは存在しませんので、不用意にお気に入り登録されると具合の悪い事になります。そこでハッシュリンクをセットするろ方法がお薦めです。
メッセージ通信(HTML5 Web Messaging)
window間で相互にオブジェクト(*1)を送受信する事ができます。この通信はクロス・ドメイン間でも可能です。
信頼出来る別ドメインのウェブ・アプリ間でデータの授受が必用な場合に用います。
なお、信頼できるドメインの確認ロジックを組み込む必用があります。これを慎重に組み込み、不用意にデータ詐取されことを防止する必用があります。
以下に、ちゅんラヂとそのエリア選択との間で、エリア選択結果の授受を行っいる事例を上げておきます。この事例では信頼できるoriginを確認しなくても、不正利用されて困るようなデータ授受はありません。しかし不正な利用は気持ち悪いことから、信頼できるoriginの確認を行っています。
1.送信側:エリア選択画面(tunein2area.html内)
function sendAreaCode(aArea){
var winTarget = null;
// 親Window オブジェクト
if (window.opener == null && window.parent == window.self) {
console.log("tunein2area.htmlの単独利用では選択エリアを戻すwindowがありません。");
return false;
}
if(window.parent !== window.self) {
winTarget = window.parent;
} else if (window.opener !== window.self) {
winTarget = window.opener;
} else {
console.log("tunein2area.htmlは親windowへ送信する要件のみです。それ以外のwindowへ選択エリアを戻す事は出来ません。");
return false;
}
//親windowのみに選択されたエリア情報を送信
if (window.location.origin == "null" ) {
winTarget.postMessage( aArea , "*" );
} else {
winTarget.postMessage( aArea , window.location.origin );
}
console.log("tunein2areaで選択されたエリア:" + aArea);
return false; //falseはクリック元のアンカーの動きをキャンセル
}
2.受信側:ちゅんラヂ画面(tunein2radio.html)
/* --------------------------------------------------------------------------------------------
tunein2area.htmlから選択されたエリア情報のpostMasseageを受けてエリアを切り替える
*/
// postMasseageの着信を監視
if(window.addEventListener){
window.addEventListener("message" , receiveArea);
}else if(window.attachEvent){
window.attachEvent("onmessage" , receiveArea);
}
//受信イベント処理
var receiveArea = function (e) {
// 信頼するorignからのデータであることを確認
if (e.source.location.origin == "null" //local実行 ※Firefoxのみ動作
|| e.source.location.origin == "https://www.zdesign.info"
|| e.source.location.origin == "http://www.zdesign.info"
|| e.source.location.origin == "https://zdesign.info"
|| e.source.location.origin == "http://zdesign.info") {
if(e.source.location.origin != location.origin) {
// 同一orign以外からの受信で、tunein2area.htmlが信頼できるorigin以外に設置されている
console.log("送信元のoriginは信頼できるが当該受信originと不一致(warning): 送信元origin:「" + e.source.location.origin + "」 受信元origin:「" + location.origin + "」");
} else {
// 送受信双方とも同一orignで信頼できる
console.log("送信元のoriginが一致(OK): 送信元origin:「" + e.source.location.origin + "」 受信側origin:「" + location.origin + "」");
}
} else {
// 送信元のwindow.location.orignが信頼できない(成りすまし)
console.log("送信元のoriginが信頼できない(reject) 送信元origin:「" + e.source.location.origin + "」");
return false;
}
// 送信されてきたエリアに切り替える
・・・エリアを切り替える処理記述
}
iframeで機能単位に分割
ちゅんラヂはiframeを多様しています。iframeを多様することは、文書としてのHTMLでは好ましくありません。しかし、ちゅんラヂの様に機能(ウェブ・アプリ)の場合は、機能毎に分割管理することで、一つのHTMLが複雑化・肥大化することを防ぎ、メンテナンス性の悪化が防止できます。
なお、各機能のHTML(window)間のデータ授受はメッセージ通信を利用しています。
また「allow="autoplay; encrypted-media"」属性を指定することでautoplay blockingの回避が容易にできます。ただし親Windowで何らかのユーザー操作が無い状態でのautoplayは出来なくなります。これは各ブラウザに於いてautoplay poricyの実装が進んでいる為です。
audio要素とMedia Source Extensions(MSE)を通す場合での「Access-Control-Allow-Origin」の扱いに違いが
ちゅんラヂの様にHLSやMPEG-DASHのストリーミング音声配信を受信する為にMSEを経由する時の話です。
MSEを通す場合audio要素のcrosプロパティに'anonymous'をセットすることで対応できました。しかしこれでも駄目な配信がありました。
なぜかと言うとMSEを利用する場合はストリーミングデータををXHRのクロスドメインの制限をうけることになるからです。
そこでテストを行うと配信サーバーから送られてくるHTTP HeaderでAccess-Control-Allow-Originの設定有無で、双方の解釈に違いが有ることが判りました。
以下の表はちゅんラヂの手法から見た場合の事象です。
┌────────────────┬────────┬─────────────┐
│Access-Control-Allow-Origin設定 │ audio要素 │ Media Source Extensions │
┝━━━━━━━━━━━━━━━━┿━━━━━━━━┿━━━━━━━━━━━━━┥
│具体的なorigin設定あり *2 │ 接続出来ない │ 接続出来ない │
├────────────────┼────────┼─────────────┤
│* 又はリクエストoriginが戻る │ 接続出来る │ 接続出来る │
├────────────────┼────────┼─────────────┤
│当該Header無し *1 │ 接続出来る │ 接続出来ない │
└────────────────┴────────┴─────────────┘
主なサイマル配信のクロスドメイン許可状況
┌───────┬──────────────┬─┬─┬────────────┐
│ 配信元 │Access-Control-Allow-Origin:│*1│*2│ 備考 │
┝━━━━━━━┿━━━━━━━━━━━━━━┿━┿━┿━━━━━━━━━━━━┥
│らじる★らじる│リクエストoriginが戻る │○│○│HLS(HE-AAC) │
├───────┼──────────────┼─┼─┼────────────┤
│radiko.jp │radiko.jp │×│○│HLS,IE向けはRTMPE │
├───────┼──────────────┼─┼─┼────────────┤
│JCBA │* │○│○│HLS(HE-AACv2)とicy(MP3) │
├───────┼──────────────┼─┼─┼────────────┤
│ListenRadio │リクエストoriginを戻る │○│○│HLS(HE-AAC) │
├───────┼──────────────┼─┼─┼────────────┤
│AFN │リクエストoriginを戻る │○│○│icy(MP3) │
└───────┴──────────────┴─┴─┴────────────┘
参考:Security with MediaElementAudioSourceNode and Cross-Origin Resources
混在コンテンツ制限で使えないWeb APIサービス
ちゅんラヂではIPアドレスからエリアを自動判定する為に「http://ip-api.com/json/」サービスを利用しています。
ここでip-api.comがTLSに対応していないために、ちゅんラヂに「https://~」でアクセスすると混在コンテンツになり利用できません。そこでWeb Serverに中継させることで混在コンテンツを回避しました。
それを回避する為の中継プログラムはPHPで記述しました。コードは下記の2行です。
<?php
echo file_get_contents("http://ip-api.com/json/" . $_SERVER["REMOTE_ADDR"]);
たった2行のプログラムを解説するのは野暮というものですので、解説は割愛します。
ベタですが、スムース・スクロールをこの備忘録で利用しています。私はページ内リンクがスパッスパッと切り替わるのが好みです。しかし、それはページ構成がわかっている場合の話です。スムース・スクロールさせることで、感覚的にページ内を上下方向のどちらに動いたのかがわかり、ページ内で迷子にならないのが利点です。
なおスクロールでジャンプさせるアンカーですが、今どきはページ内アンカー「<a name="アンカー名"> </a>」を使わずに、id属性がページ内アンカーの役割をします。
コードは下記の通りで、スムース・スクロールはjQueryのanimateで行っています。“jquery.easing.1.3.js”はjQueryのアニメート・エフェクトに変化を加えるjQuery Easing Pluginです。
<script src="../libs/jquery.easing.1.3.js"></script>
<script>
$(function () {
//スムース・スクロール
$('a[href^="#"]').click(function() {
var speed = 700; //ms単位でスクロール速度を指定
var href = $(this).attr("href");
var target = $(href == "#" || href == "" ? 'html' : href);
var position = target.offset().top;
$('body,html').animate({
scrollTop: position
},{
duration: speed, easing: 'easeInCubic'
});
history.pushState(href,null,href); //historyにアンカーへの移動を記録
return false; //本来のアンカーの動きを封じる
});
});
</script>
なっからHPのコンテンツはタブで切り替えています。そしてHTML的にはiframeを除き1ページ構成で、同一ページ内のブロックの表示・非表示を切り替えています。利点はコンテンツ切替時のページ読み込みがありませんので、切り替えは瞬時に切り替わります。
この手法はなっからHPの様に、コンテンツ量が少ないウェブページの場合に利点が活かせます。大きなコンテンツの場合は初期ロードに時間がかかりますので、この手法は短所になります。
ソースコードの要点です
<nav id="tab_menu">
<ul class="tabul">
<li id="tab1" class="tabPresent">
コンテンツ1
</li>
<li id="tab2">
コンテンツ2
</li>
<li id="tab3">
コンテンツ3
</li>
<li id="tab4">
コンテンツ4
</li>
</ul>
</nav>
<section id="sec1">
:
コンテンツ1
:
</section>
<section id="sec2" style="display: none;">
:
コンテンツ2
:
</section>
<section id="sec3" style="display: none;">
:
コンテンツ3
:
</section>
<section id="sec4" style="display: none;">
:
コンテンツ4
:
</section>
/* タブメニュー */
#tab_menu {
position: absolute;
bottom: -1px;
clear:both;
overflow: hidden;
width:880px;
height: 32px;
}
#tab_menu ul{
list-style:none;
margin: 0;
position: absolute;
bottom: 0px;
}
#tab_menu li {
float: left;
display: block;
font-size:14px;
line-height: 100%;
text-align:center;
vertical-align:middle;
text-decoration: none;
height: 22px;
margin: 0px 1px 0 1px;
padding: 8px 0 0 0;
border-top: 1px solid gray;
border-left: 1px solid gray;
border-right: 1px solid gray;
border-bottom: 1px solid navy;
border-radius: 6px 6px 0 0 / 6px 6px 0 0;
background-color: lightsteelblue;
}
#tab_menu li:hover {
background-color: blue;
color: white;
cursor: pointer;
}
#tab_menu li:hover.tabPresent {
background-color: white;
color: navy;
cursor: auto;
}
#tab_menu li.tabPresent {
display: block;
line-height: 100%;
text-align: center;
vertical-align: middle;
border-top: 1px solid navy;
border-left: 1px solid navy;
border-right: 1px solid navy;
border-bottom: 1px solid white;
background-color: white;
}
#tab_menu li.tabPresent a {
background-color: white;
color: navy;
cursor: default;
}
var gTab = {
init : function () {
var pTabs = this.setup.tabs;
var pPages = this.setup.pages;
var i;
for(i=0; i<pPages.length; i++) {
if(i !== 0) pPages[i].style.display = "none";
pTabs[i].onclick = function(){ gTab.showpage(this); return false;}
}
},
showpage : function (aTab) {
for (var i = 0; i < this.setup.tabs.length; i++) {
if (this.setup.tabs[i].id == aTab.id) {
this.setup.pages[i].style.display = "block"; //クリックされたタブのブロクを表示
this.setup.tabs[i].className = "tabPresent" //クリックされたタブのclassを変更してカレント状態表示にする
presentIx = i;
} else {
this.setup.pages[i].style.display = "none"; //クリックされたタブの以外のブロクを非表示
this.setup.tabs[i].className = null; //クリックされたタブの以外のクラス名をクリアーして選択されていない表示状態にする
}
}
}
};
window.onload = function () {
//タブメニューのセットアップ
gTab.setup = {
tabs : docid$("tab_menu").getElementsByTagName("li"),
pages : [docid$("sec1"), docid$("sec2"), docid$("sec3"), docid$("sec4")]
}
gTab.init();
};
フォント種類とフォントサイズ&ウェイトの指定方法
基準となるフォントのサイズと相対的なフォントサイズの指定で、全体的な文字サイズのバランスをそのままで拡大・縮小ができる様にする。
つまり全体的な文字サイズを変えたい時には、html要素のcss指定を変えるだけで、配下の各要素のフォントサイズを変更する必要がなくなる。
基準となるフォントサイズ(em指定)はhtml要素に指定する。
以下の例ではGoogleが提供するウェブ・フォント「Noto Sans Japanese」を利用して、基準をなるサイズをウェイトを指定している。
ウェブ・フォントを読み込むことで表示までの時間が掛かるが、プラットフォームに依存しない文字表示が可能になる。
「arial」指定は、何らかの理由でGoogleからフォントが読み込めなかった時の代替えフォント指定。
html要素は以下の相対フォントサイズ(rem指定)は指定する。
基準フォントに対す相対的なサイズを指定することで、全体のフォントサイズ変更時の変更作業を最小限にする考慮を行う。
@import url(https://fonts.googleapis.com/earlyaccess/notosansjapanese.css);
html{
width: 100%;
font-size: 1.0em;
font-weight: 200;
font-family: 'Noto Sans Japanese',arial;
}
body{
font-size: 1.0rem;
line-height: 1.5rem;
}
埋め込みフォントを止めました。
Google Fontを埋め込んだ時は、ウェブ・フォントの読み込み時間が1秒程度で問題ないと判断したのですが、最近は読み込みに5秒以上かかる様になってしましました。私は待ち時間5秒を超えたら実用性に疑問を感じますので一旦、ウェブ・フォント埋め込みを止めました。
読み込みが遅くなった原因に心当たりがあります。私が利用しているプロバイダに問題が有るようです。春頃から夕方以降にダウンロード速度が30Kbps程度まで悪化しています。このプロバイダは老舗でパソコン通信時代から使っているメールアドレスがあり止めるに止められない状況です。
私の環境に問題が有るのは明白ですが、他にも私のような環境の方が居られると思い、しばらく様子見で埋め込みフォントは見合わせます。
複数のYouTube動画を埋め込む時はクリック後に動的再生で
「何いってんだ!」と言わないでください。YouTubeの埋め込みはiframeで読み込まれるコードが結構重い。これを複数設置するとローディングに時間がかかります。ということで複数埋め込むべきでは無いというお話です。
そこで、なっからHPでは直接埋め込まずに、一覧はイメージを並べて、そのイメージがクリックされた時に動的に埋め込みコードを生成しています。イメージをクリックした時にpopup風のブロックを動的に作り出して、そこにYouTube動画埋め込みコードを書き出し、再生しています。
loadingやsending待ちの時には不用意な操作を防止させる
なっからHPではローディングが終わるまで、待っている事を知らせるアイコンが表示しウィンドウ全体を覆うことで、不用意な操作を防止しています。
静的なコンテンツだけではなく、動的にJSONデータを読み込んでコンテンツを表示していますので、その処理が完了する前にメニューをクリックされた場合の動作が不定になる可能性があります。これを防止する為です。
入力フォームに入力されたデータをサーバーに送信した場合にも、この手法でサーバーからの応答が戻るまで不用意な操作を抑止することで、せっかちは方が送信ボタンを何度もクリックして重複送信になってしまう事を防げます。
ソースコードの要点です
<div id="loading">
<div id="loading_circle">
<img src="./img/loading.gif" alt="loading">
</div>
</div>
/* Loading Mask ロードされるまで操作されたくない */
#loading {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: white;
opacity: 0.7;
z-index: 1000;
}
#loading_circle {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
width: 48px;
height: 48px;
z-index: 1001;
}
$('#loading_circle').hide();
$('#loading').hide();
bxSlider:スライダーが簡単に設置できる
jQueryプラグインのbxSliderは高性能なコンテンツ・スライダーでレスポンシヴ・デザインにも対応しています。
まずはbxSliderを入手しましょう。 bxslider-4
こちらの利用方法を参照してください。設定項目が盛りだくさんです。
インターネットラジオを自動再生させる
Google ChromeはAutoplay Policy ChangesでChrome 66から自動再生が出来なくなりました。これはサイトを開いたら音が出てビックリを無くす処置です。
これにより、ちゅんラヂは「~URL~?recieve=放送局ID」で開くと同時に自動再生をさせる機能がChromeでは使えなくなってしまいました。
しかしYouTubeは相変わらず自動再生が出来ています。これ謎です。YouTubeが自動再生できるのだから、ちゅんラヂだって出来る筈と思い色々と試してみました。
行ったことはaudioタグの属性に「muted」を指定しておき、JavaScriptにて再生時にそのミュートを解除する。更にちゅんラジ・プレイヤーを埋め込んであるiframeにautoplay属性を与える。
しかし駄目でした。YouTubeの動画が表示される部分を解析してみましたが謎のままです。GoogleのYouTubeだけ特例で認めるのってズルい気がします。
ところで、何度か手動再生を行ったサイトからの自動再生が可能になります。これはMedia Engagement Indexで管理され「chrome://media-engagement/」で確認できます。このscoreが高くなると自動再生を認めるようです。
Chromeの設定を以下の手順で変更すると以前の状態に戻り自動再生が有効になります。
なお、Firefoxの「media.autoplay.esabled」はfalseにすると自動再生ができなくなります。Chromeの様にiframeを介してた自動再生も出来なくなります。
利用しているHTML Editor (Sublime Text 3)
HTML構文の入力にはSublime Text 3を利用しています。
Sublime Text 3はHTML補完機能に優れています。タグ候補はHTML5対応です。終了タグは“</”と入力すると自動で補完されます。CSSファイルとの連携で対応する行表示などは感動的ですらあります。慣れるとHTMLページを整える作業が効率よくできます。
なお既定値のままでは日本語入力が使いづらいです。そこで日本語のインライン入力用にIMESupportをインストール(*1)します。
そして既定値ではShift-Jisが扱えませんのでConvertToUTF8もインストール(*1)します。
それと既定のフォントと行間表示が日本語用には見づらいものです。そこで下記の様に、基本設定のユーザー設定欄に打ち込み、見やすくしました。
さらに、フォルダからファイルをクリックした時に開くプレビュー機能は慣れないと使いづらい事から、これを切ります。因みにSublime Textに非常に似た機能のVisual Studio Codeというエディッタのプレビュー機能も使いづらいです。こちらは設定でEditorもworkbench> Editor:enablePreviewのチェックを外す事でします。
昔書いたコマンドラインプログラム(C言語)のソースをSublime Textで開いたら、これも感動的でした。
その頃にSublime Textがあれば楽だったのにと感じました。
コマンドラインプログラム大好きな虫が、また復活しそう。(*^^*)
Sublime Textは定期的に購入催促が表示されます。購入催促されても使い続けられますので、気が済むまで試用を続ければ良いと思います。きっとライセンス購入しても良いという思いになります。
なおMSのVisual StudioのHTML Editorも使ってみましたが鈍足でHTML補完も文字入力もかなり不満でした。
因みにWysiwyg系のHTML Editorは手打ち派の私にとっては有り得ないようなソースコードが吐出されるので使いません。ソースなんて見ないから気にしない派の人ならWysiwyg系のHTML Editorが良いと思います。
Adobe Dreamweaverとか使える環境の方は、それがベストだと思います。私は、その操作を覚えるのが面倒なのでSublime Textを使う手打ち派です。
Firefoxの不可解な2回リクエスト
ちゅんラヂあいる(HTTPサーバー)という、ローカルで受信したネットラジオのストリームをちゅんラヂ(ブラウザ)に向けて流し込むプログラムを作成しています。簡単に言えばHTMLとWebサーバーの組み合わせでネットラジオを受信しようという魂胆です。ちゅんラヂあいるを公開できる日が来るかは未定です。Web形態に拘っていますので、こんなややこしい構成にしています。目標は国内のサイマルラジオを一堂に全部を集めてザッピング出来うようにすることです。
閑話休題:
ちゅんラヂあいる作成中にFirefoxの不具合に悩まされました。間違いなく1回しかリクエストしていないのに、サーバー側には2回もリクエストが届くのです。(・_・) 確認しているFirefoxのバージョンは61.0 64bit版です。
Webアプリにとっては同じリクエストが2回も同時に届くなんて致命的です。セッション管理して、ほぼ同時に届いた処理をぐちゃぐちゃにならないようにすれば済む話です。しかしラジオを受信するだけのお遊びソフトでそこまでやってもしょうがない、それにFirefoxの不具合を根本的に解消できることにはなりません。それにサーバーはnode.jsを素のまま使っているだけで、至って簡素なプログラムです。自分用でms単位で同じ処理のリクエストなんて想定してもスペックオーバーになるだけです。
検索してみるとhrefやsrcを空欄にした場合や、レンダリングが2回さるようなHTMLページで2回リクエストが発生している様です。しかし、今回のケースはHTMLページが存在するわけではなく、URLでサーバーにリクエストするだけの物です。ヒットしたケースとは別物です。
色々試していると同じ種類のリクエストでも正常に1回だけの場合もあります。リクエスト方法を色々が変えてもみました。表が駄目ならXMLHttpRequestにて裏側でリクエストも行ってみました。しかし何をやっても症状の改善がみられません。もう2日も弄り回して回避策を探りました。しかしさっぱり判りませんでした。
そこで、上手くゆくリクエストと駄目なリクエストのURLをじっくり比較してみました。そして発見しました。語尾に「.m3u8」がある場合に、Firefoxが勝手に2回リクエストを行っていました。
http://~hoge.html?url=~.m3u8 ※このリクエストを行うと2回も送信されてしまう
http://~hoge.html?url=~_m3u8 ※ドットの部分をアンダースコアに補正すると問題なし
そこでサーバー側の処理にて「_m3u8」→「.m3u8」変換を行うことで、Firefoxの不可解な動作が回避しました。
レアなケースではありますが、これは厄介な不具合です。Firefoxはm3u8に対して何をしようとしているのでしょうか。判ったからといって自力でFirefoxのこの問題を改修できるはずもありません。とりあえずMozillaにフィードバックで送信しておきました。(^^ゞ
それに、ちゅんラヂあいる環境は自分専用で公開の目処が立っていないですし(^○^)
しかし、こんな事があってもFirefoxはメインで使うブラウザです。シンプルな考え方が好きなブラウザです。私はFirefoxを中心に、Chromeで動作確認を行うようにしています。Edgeは気が向いたら時々。IEに至ってはWebアプリはほとんど動かないことが多く敢えて動作確認対象から除外しています。ビジネス用でIEでの動作が求められる場合以外は無視です。
Firefoxがクロスドメインじゃないのにクロスドメインだと宣う。
ついでにクロスドメインだった時のnode.jsでのプリフライトリクエスト対応。
Firefox+jQueryの$.ajaxで不可解な動作に遭遇しました。それはFirefoxがXMLHttpRequestで「クロスオリジン要求をブロックしました」となる不可解な動作のお話です。
エラー内容
クロスオリジン要求をブロックしました: 同一生成元ポリシーにより、http://127.0.0.1:8071/play?url=http%3A%2F%2Fedge-ads-02-gos1.sharp-stream.com%2Fjazzfmmobile.aac%3Fdevice%3Drpweb にあるリモートリソースの読み込みは拒否されます (理由: CORS 要求が成功しなかった)。」
現象
Firefoxは上記のURLのをサーバーに送信すらせずにエラーを吐きます。そりゃないよ!!。因みにリクエスト先はクロスドメインではありません。
Google Chromeは同じ条件でサーバーにリクエストを送信してくれます。Firefoxでも上記URLの引数「url=」の後ろが上記以外ならリクエストを送信してくれます。
動作環境
ウェブサーバーはnode.jsです。
ブラウザ側のXMLHttpRequestはjQueryの「$.ajax」を利用しています。
ブラウザのアドレス入力欄に直接入力してもエラーになります。
対策方法
GETリクエストをやめて、POSTリクエストで「url=」の引数を渡すようにすることで、Firefoxの不可解な現象を回避できました。
結果
GETからPOSTに変更したことでFirefoxの不可解な現象が解消しました。
なお、以前悩んだFirefoxの「.m3u8」の不可解な動作もこれで回避できることがわかりました。
教訓ですが、GETリクエストの長々としたURL引数で情報を送らずに、素直にPOSTで情報を送ったほうがベターだということです。その時の送る情報はJSON書式のプレーンテキストで送ると、データの授受の処理が楽になります。
本当にクロスドメインPOSTだった時にはどうする!!【node.jsの場合】
実はテスト段階では同一ドメインですが、公開段階ではクロスドメインになりますので、同一オリジンポリシーに引っかかり動作しません。そこでサーバー側で対応する必要があります。
クロスドメインでのPOSTはブラウザが先にOPTIONSメソッドでプリフライトリクエストを送信します。node.jsは低レベルなHTTPサーバー機能ですので、プリフライトリクエストに自動的に応答する機能がありません。そこでnode.jsのJavaScriptでOPTIONSリクエストが来たら、以下の様に許可するレスポンスを返さないと、POSTリクエストをブラウザが送信してくれません。
if (req.method === 'OPTIONS') {
var headers = {};
headers["Access-Control-Allow-Origin"] = "*";
headers["Access-Control-Allow-Methods"] = "POST, GET, PUT, DELETE, OPTIONS";
headers["Access-Control-Allow-Credentials"] = false;
headers["Access-Control-Max-Age"] = '86400'; // 24 hours
headers["Access-Control-Allow-Headers"] = "X-Requested-With, X-HTTP-Method-Override, Content-Type, Accept";
res.writeHead(200, headers);
res.end();
} else {
//...other requests
}
上記で返すヘッダーの内容は必要最低限の許可を返します。この情報はここで得られました。有難う御座いました。
なおContent-Typeを許可してもFirefoxはクロスドメインで本来許可されているタイプ以外は拒絶(CSRF対策?)します。詳しくはオリジン間リソース共有を参照して下さい。そこでデータはJSONオブジェクトではなく、JSON書式のプレーンテキストで送ることで対処する必要があります。
ところでCSRF対策は、ここで作成しているサーバー(node.js)はlocalhost限定物で割愛しています。公開する時には当然ですがCSRF対策しないと駄目です。
音量調整スライダーの可変量と耳で感じる音量変化の感じを近づける
ちゅんラヂ・プレイヤーの音量調整スライダーのお話です。Web Audio APIの音量値を直線的な値で変化させると耳で感じる音量変化に対して不自然でした。スライダー最小からの音量変化が大きく、スライダーの半分くらいから上で緩やかな音量変化に耳では感じます。耳で感じる変化量が対数グラフっぽい感じです。これでは小さい方の音量調整に不満を感じました。
そこでスライダーの値が小さい方での音量値の変化が小さく、スライダーの値の大きな方で音量値の変化が大きくすることで、耳で感じる変化量が自然になるのではないかと思いました。
後で知ったのですがAカーブにすることで私の不満が解消するのだと知りました。数値的に直線的なのがBカーブ。対数的な変化がCカーブなのだそうです。
スライダーの変化範囲を0~100で1ステップ毎の変化としました。これを音量値0~1(*1)に間に収まる様に計算させました。
計算式は対数計算値の上下をひっくり返して左右反転させました。数学は苦手なのでこの表現で良いのかはは?です。結果として音量調整スライダーの可変量と耳で感じる音量変化に不自然さを感じる事がなくなりました。
参考にしてしたサイト
Aカーブ,Bカーブ,Cカーブが直感的にわかる
電子ボリュームICの減衰特性(A:対数,B:直線,C:逆対数など)を計算する。
Controlling Volume—log potsの中のFigure.3の抵抗を並列に入れて底上げする方法も試しました。
Google Chromeの頑固なキャッシュを回避
ウェブページを変更し時にhtmlファイルは直ぐに反映されましたがCSSがキャッシュされたままで表示が崩れる現象に遭遇しました。Cstl+Shift+RでリロードさせてもCSSのキャッシュが無効になりませんでした。設定の中の閲覧履歴を消去でやっとCSSが削除されて表示が最新になりました。しかしウェブページを訪れるユーザーはそんな事はしません。
そこでCCSの読み込みURLの引数に変更時日時を指定する事でキャッシュ回避を行いました。仕組み的には引数の値が変わればキャッシュが別もになり読み込みが行われます。
例:<link rel="stylesheet" href="./css/index.css?time=1539181239" type="text/css">
「1539181239」の部分には私は更新時点のUNIXタイムをセットしました。
JSONPがキャッシュで更新されないとき
JSONPデータのURLに日時を付加してキャッシュを逃れます。
なんとなくイケてるモザイク配置
整然と並んだグリッド配列と違って、コンテンツの高さに応じてフレキシブルに配置されるモザイク配列。見やすいかは別としてフレキシブルなのでイイ。
なっからHPの「ちゃちゃっと麺作り」の部分がモザイク配列になっています。
HTMLの事例
<div class="mosaic_container">
<div class="mosaic_item">
<h2>・・・・・・< /h2>
・・・ここにブロック内のコンテンツを記述
</div>
<div class="mosaic_item">
<h2>・・・・・・< /h2>
・・・ここにブロック内のコンテンツを記述
</div>
<div class="mosaic_item">
<h2>・・・・・・< /h2>
・・・ここにブロック内のコンテンツを記述
</div>
</div>
上記HTMLに対するCSSの例(レスポンシブ対応)
/* モザイク配置を行うコンテナ */
.mosaic_container {
margin: 0 auto;
width: 930px;
column-count: 3;
column-gap: 10px;
padding-right: 10px;
}
@media screen and (max-width: 700px){
.mosaic_container {
margin: 0 auto;
width: 310px;
padding-right: 10px;
column-count: 1;
}
}
@media screen and (min-width: 700px) and (max-width: 970px){
.mosaic_container {
margin: 0 auto;
width: 620px;
padding-right: 10px;
column-count: 2;
}
}
/* モザイク配置の中のアイテム */
.mosaic_item {
width: 250px;
margin-top: 0;
margin-bottom: 15px;
margin-left: auto;
margin-right: auto;
padding: 1px 10px 10px;
border-radius: 6px;
-webkit-column-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
}
レスポンシブ対応(パソコンとスマホに一つのHTMLページで対応) ※記述中
ウェブはスマホからの閲覧が増えています。ホームページはスマホにも対応しないといけないのかもしれません。
そこで「なっから」サイトもレスポンシブ対応させてみました。条件はW3C準拠のブラウザのみを対象にします。i-Modeなどの古いモバイル環境には対応しません。パソコンもIEは11のみに簡易対応(表示が多少崩れても気にしない)としました。
デザイン的には文字やブロックが表示枠幅によってニュルニュルーと回り込むリキッド型にすることにしました。ソリッド型はかっこいいけれどそこまで凝る程のコンテンツじゃないかなと思いパスです。トップページのみイメージ背景を使って居ますので、背景と文字サイズを表示枠幅に対してサイズを可変にしてみました。スマホでは表示サイズが小さくなりすぎてどうかなとも思いましたが、これは技術的な興味を優先しました。
対応するのに苦労したのは、安直に絶対位置で配置してある所を考え直す必要があった事です。そしてブロックも後のことを考えずに行き当たりばったりで作成していましたので、これも改める必要がありました。
それと一番厄介だっのが不用意にmargin-leftとmargin-rightを使っていたことです。padding-leftとpadding-rightも同様です。なぜならば親ブロックの幅を無頓着にはみ出した部分が多々有ったからです。はみ出ていると、スマホで表示した時に画面幅にピタッとフットしません。そして何処がはみ出ているのかを探すのに一苦労でした。
(1). CSSなしでベタな文書として通用する章立てになるように改めた。
例えば、下記の様なHTMLタグで、普通の文書としてコンテンツを書いてゆきます。この段階では文書の見た目は気にせずに章立てのみを気にして書いて行くのが吉です。
<html>
<head>
</head>
<body>
<header>
ここには文書の頭書きを書きます。
</header>
<article>
<h1>・・章の見出し・・</h1>
ここには文書の章を書きます。
<section>
<h2>・・節の見出し・・</h2>
<p>
ここには文書の節を書きます。
</p>
<section>
<h3>・・節の中の節の見出し・・</h3>
<p>
ここには文書の節の中の節を書きます。
</p>
<aside>
<h3>・・補足の見出し・・</h3>
<p>
ここには文書中の補足などを書きます。
</p>
</aside>
</section>
</article>
</body>
</footer>
ここには文書の脚書きを記述します。
<footer>
HTML5では文章の章や節を表わせるタグが登場して、HTML4以前と違い、文章としての構成をよりわかりやすく記述できます。
ここはあまり肩苦しく考えすぎると手が止まりますので、ワープロのスタイルで見出した段落を整える感覚でタグを利用すれば良いのです。
レスポンシブ化を阻害するのは、ワープロでいうところのスタイルを使わずに、文字属性を直接弄って整える様なHTML文書を書くと、レスポンシブ対応が困難になります。
(2).大枠(bdoy)以外は相対幅に改めた。%指定もしくはvw指定にしてみた。
(3).フォントサイズはHTMLタグで1.0emとして、その配下は相対的なrem指定に改めた
リキッドデザインと言われている、ブラウザの表示幅を変えると、ニュルニュルと折り返される文書にするには、書く章立てを絶対値で幅をしない様にします。
html {
width: 100%;
height: 100%;
font-size: 1.0em;
}
body {
width; 95%;
height: auto;
}
article {
width: 100%;
height: auto;
}
h1 {
font-size: 1.2rem;
dont-weight: bold;
}
sction {
margin-left: 1.0rem;
}
h2 {
font-size: 1.1rem;
dont-weight: bold;
}
aside {
margin-left: 1.0rem;
}
p {
text-indent: 1.0rem;
}
上記のCSSはワープとでいうところの見出しや章立ての見え方のスタイルを指定するものです。
文書だけなら、これだけで立派なリキッドデザインのレスポンシブ対応完了です。つまりワープロ文書をスタイルで整えていれば、余白幅を変えようが、用紙設定を縦から横に変えても、文書の見た目が崩れないのと同じ考え方です。
それとhtmlタグでフォントサイズと指定しておき、その配下のタグ中のフォントサイズはrem(親要素のフォントサイズに対する相対サイズ指定)で、文字サイズのバランスを整えます。こうすることでバランスを崩さずに、htmlタグのフォントサイズを変えるだけで、全体の文字サイズを大きくしたり、小さくしたりすることが自由自在です。
(4).イメージも相対指定に改めた
イメージも固定な絶対値サイズではなく、表示枠サイズに合わせて可変表示にします。
コンテンツの中に部分的に表示している場合
.variable_img {
margin-left: 10px;
width: auto;
height: auto;
}
@media screen and (max-width: 720px){
.variable_img {
max-width: 95%;
height: auto;
}
}
ブロック内で幅や高さが目一杯表示している背景の場合
background: #fff url("../img/hoge.jpg") 0 0 no-repeat scroll;
background-repeat: no-repeat;
background-position: left;
background-size:contain;