【JavaScript】IEのためにa要素のdownload属性を代替する

a要素のdownload属性を使えばchromeなどのモダンブラウザではコンテンツをダウンロードできるわけだが、IEはそうはいかない。例えば、下の画像をダウンロードする時、IE以外のモダンブラウザではこのように書けばダウンロードされる。

var a = document.createElement('a');
a.href = '/img/1.jpg';
a.download = 'neko';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);

IEではダウンロードが実行されずにそのままページ遷移してしまうのがわかる。そこで今日は、代替方法を書く。

その方法とは、xhrでrequestを送り、blobで返してもらいそれをダウンロードするという方法だ。下がそのコードである。

var xhr = new XMLHttpRequest();
xhr.open('GET', '/img/1.png');
xhr.responseType = 'blob';

xhr.onloadend = function() {
	if(xhr.status !== 200) return;
	window.navigator.msSaveBlob(xhr.response, 'neko');
}
xhr.send();

IEにはnavigatorオブジェクトにmsSaveBlob(blob, fileName)というメソッドが用意されているようで、blobのダウンロードにはこれを使う。

もうすでにお気づきの方もいるかもしれないが、これは完全な代替方法にはなり得ない。なぜならxhrはクロスドメイン制限があるからだ。つまり、同一ドメインか、CORS(Cross-Origin Resource Sharing)に対応していないとダウンロードきない。そして、blobのやりとりはIE9以下は対応していない。しかし、使える機会はあると思うので、もしよかったら使ってほしい。

他にいい方法を知っているという方はぜひ教えてください><

【JavaScript】HistoryAPIを使った非同期遷移なサイトにハッシュ(#)を含むURLでアクセスされた時

非同期遷移なサイトにハッシュ(#)を含むURLでアクセスされた時、目的の要素までスクロールするという処理を自分で実装しないといけないことがある。例えば、http://example.com/aaa/からhttp://example.com/bbb/#cccに非同期遷移した場合、当たり前だが、ブラウザはid=”ccc”の要素までスクロールしない。そこで、今回は自分で目的要素までスクロールする処理を考えてみる。

自分で実装と言ってもそこまで難しくはなく、スクロールするだけなら単にAPIで用意されている、elementのscrollIntoViewメソッドを呼べばいい。つまり、ハッシュの文字列を得て、該当の要素のscrollIntoViewメソッドを呼ぶ。

element.scrollIntoView

function scrollIntoViewOf(hash) {
	var element = document.getElementById(hash);
	if(element === null) {
		return;
	}
	element.scrollIntoView();
}

もちろんinnerHTMLなどで非同期で取得した要素に切り替えた後に呼ばないとスクロールしない。しかし、切り替えた要素にimg要素が含まれている場合、img要素のsrc属性は非同期で呼ばれるため、正しい位置にスクロールしてくれない。画像が読み込まれる前と読み込まれた後では要素の高さが違うためである。つまり、画像が全て読み込まれた後にscrollIntoViewを呼び出す必要がある。そこで、前回の記事で紹介した「全ての画像を読み込んでから処理を実行するimagesLoadListener」が使える。

function scrollIntoViewOf(hash, imagesLoadListener, replacedElement) {
	var element = document.getElementById(hash);
	if(element === null) {
		return;
	}
	imagesLoadListener(replacedElement, function() {
		element.scrollIntoView();
	});
}

これで正しい位置までスクロールしてくれるはずだ。

【JavaScript】全ての画像を読み込んでから処理を実行する

要素内にimg要素がある時、全ての画像が読み込まれないと要素の正確な高さを計れなかったりする。そういう時のためのスクリプト。

var imagesLoadListener = (function() {

	var imageCollector = function(expectedCount, completeFn) {
		var receivedCount = 0;
		return function() {
			receivedCount++;
			if(receivedCount === expectedCount) {
				completeFn();
			}
		};
	};

	var imagesLoadListener = function(element, callback) {
		var images = element.querySelectorAll('img');
		if(images === null) {
			callback();
			return;
		}

		//画像の数だけloadListenerが呼ばれたらcallbackが呼ばれる;
		var loadListener = imageCollector(images.length, callback);
		Array.prototype.forEach.call(images, function(img) {
			if(img.complete) {
				loadListener();
			}else {
				img.onload = loadListener;
			}
		});
	};

	return imagesLoadListener;
})();

imagesLoadListenerの第一引数はimg要素を含んでいる要素を指定する。第二引数は画像が全て読み込めた時に実行する処理を指定する。例えば第一引数にdocument.bodyを指定したら、body内にあるimg要素全て読み込むのを待ってから第二引数の処理が実行される。

imagesLoadListener(document.body, function() {alert('Done!!')});

See the Pen imagesLoadListener by shigure (@webkatu) on CodePen.

すでに読み込んでいる画像はimg要素のcomplete属性で判断している。まだ画像が読み込めていないimg要素はloadイベント発火まで待つ。

第一引数の要素内に画像がない場合、第二引数の処理はすぐ実行される。

モダンブラウザのみ対応。

参考: javascript – Can I sync up multiple image onload calls? – Stack Overflow

【JavaScript】Arrayに数字以外のキーを持たせるべきではない

自分に対する戒めの記事

JavaScriptはオブジェクトであればArrayだろうとFunctionだろうとキーを持たせることが出来る ゆえにArrayでも数字以外のキーも持たせることが出来る。

なぜこんなことを書いているかというとArrayをJSONに変換する時にこの失敗に気付いてしまったからだ。

JSON.strigifyでArrayを文字列化したとき、数字以外のキーは拾われない。その同じArrayをJSON.parseすると見事に数字以外のキーが無くなった配列が返される

これはWebStorageを使う時などに深刻なバグになりかねない。

だからArrayに数字以外のキーを持たせるのではなくObjectに数字のキーを持たせる「ArrayのようなObject」にすべきだ。

JavaScript associative array to JSON

【Windows】新しいユーザーにログオンできない、強制ログオフになる、ログオンできても設定が保存されない問題について

今日、Windows7に新しいユーザーを作成しようとしたところトラブルが発生したので解決策含め記述していく。

症状

  • 新しいユーザー(権限:標準ユーザー)にログオンすると、すぐログオフになりログオンできない。
  • 権限を管理者ユーザーに変更するとログオンできるが、ログオフやシャットダウン・再起動を行うと設定(マウスのスピードなど)が元に戻る。
  • また、ところどころおかしい (例: タスクバーを固定するのチェックを外せない、小さいアイコンを使えない)
  • 新しいユーザーを作りなおして何度やっても同じ

考えられる原因

ユーザーのプロファイルが壊れている。新しいユーザーはC:/Users/にあるDefaultフォルダのプロファイルを元に作られるため、ユーザーを作りなおしても同じ症状が出るということはDefaultのプロファイルが壊れている。

恐らくC:/Users/Defaultの中にあるNTUSER.DATファイルが壊れている。(Defaultプロファイルは隠しフォルダのため、コントロールパネル→デスクトップのカスタマイズ→フォルダーオプション→表示タブにある「隠しファイル、隠しフォルダーおよび隠しドライブを表示する」ボタンを選択し適用すると見えるようになる。)

解決策

他の同じOSからDefaultフォルダを取得して、それを新たなDefaultプロファイルとして設定する。

具体的なやり方

  1. 他の同じOSからDefaultフォルダをコピーし、それをDefault2とリネームしてC:/Users/にいれる。(Win7のDefaultフォルダはここにおいてある。以前のアップローダーが消えたため2016/04/16変更)
  2. レジストリエディタを開き「HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList」のDefaultの値を「%SystemDrive%\Users\Default2」に変更。(レジストリエディタの開き方はスタートボタン→検索欄にregeditと入力)
  3. 再起動する。

これでも直らなかった場合のために他に関連していそうなサイトを載せておく。

他にもセキュリティソフトが原因という情報もチラホラみかけた。

今回参考にしたサイト

Windows7で新規アカウント作成 User Profile Service ログオンできない