_onichannn

【Javascript】CSSセレクタとマッチする要素を取得する。

めも。

document.querySelector()メソッドを用いる。

例えば下記のようなHTML要素を取得したい場合。

<div class="target">Click</div>
<span id="target">Click</span>

下記の通り、CSSと同様のセレクタを渡すことで要素を選択してくれる。

var div = document.querySelector('.target');
var span = document.querySelector('#target');
console.log(div, span);

document.querySelector()は同一の要素が複数ある場合、最初にマッチした要素のみを返却する。
そのため下記のように一致する要素全てを取得したい場合はdocument.querySelectorAll()を用いるとよい。

■HTML

<div class="target">Click1</div>
<div class="target">Click2</div>
<div class="target">Click3</div>
<div class="target">Click4</div>
<div class="target">Click5</div>
<div class="target">Click6</div>

■Javascript

var divs = document.querySelectorAll('.target');
console.log(divs);

 

【Javascript】キャプチャリングとバブリングの違い。

DOMのイベントには、キャプチャリングフェーズとバブリングフェーズという二つのフェーズが存在しており、addEventListenerの第三引数にて指定することが出来る。trueを渡すと、キャプチャリングフェーズ、falseを渡すとバブリングフェーズで実行される。デフォルトはバブリングフェーズで実行される。

■実行順
キャプチャリングフェーズ => バブリングフェーズ

■キャプチャリングフェーズ
DOMの外側からイベントが発生した要素に向かってイベントが伝わる。

■バブリングフェーズ
イベントが発生した要素から外側に向かってイベントが伝わる。

 

挙動に関しては要実験。

 

【Javascript】特定の要素に対して複数のイベントを設定する。

めも。

例えば以下のようなボタン要素に対して複数のイベントをバインドさせたい場合。

<button class="js_btn">Click</button>

下記の様にaddEventListener()メソッドを用いることで同時に複数のメソッドを結びつけることができる。

<script>
var obj = {};

obj.hoge = function(){
	alert('hoge');
}

obj.piyo = function(){
	alert('piyo');
}

obj.fuga = function(){
	alert('fuga');
}


$(function(){
	var btn = document.querySelector('.js_btn');
	btn.addEventListener('click', obj.hoge, false);
	btn.addEventListener('click', obj.piyo, false);
	btn.addEventListener('click', obj.fuga, false);
})
</script>

 

【PHP】anitube保存用クラスを作った。

動画保存系クラスシリーズ。主に自分用。

/**
 * anitube保存用クラス
 * 
 * $ani = new anitubeDownloader();
 * $ani->saveDir = '動画保存用ディレクトリ'; // 末端DS無し
 * $ani->tmpDir = '作業用ディレクトリ'; // 末端DS無し
 * $ani->prefix = 'ANITUBE_'; // 動画保存時のファイル名先頭プレフィックス文字列
 * $res = $ani->download('*******');
 * 
 * @author Owner
 *
 */
class anitubeDownloader {
	
	// 動画保存ディレクトリ(末端スラッシュ無し)
	public $saveDir = '';
	
	// 一時作業ディレクトリ(末端スラッシュ無し)
	public $tmpDir = '';
	
	// 動画保存時のファイル名に付与するプレフィックス文字列
	public $prefix = 'ani_';
	
	// VIDEO_ID(http://www.anitube.se/video/*******/...)
	protected $vid = '';
	
	// エラーメッセージの入れ物
	protected $errors = array();
	
	// 動画閲覧ページのベースURL
	protected $watchBaseUrl = 'http://www.anitube.se/video/';
	
	// 動画情報を取得するためのAPIURL
	protected $apiUrl = 'http://www.anitube.se/nuevo/config.php?key=';
	
	// APIURLを括るデリミタ文字(ユニークキーを含む)
	protected $delimiter = "'";
	
	// HD動画の取得に成功したか否か
	protected $isHd = false;
	
	/**
	 * ダウンロードの実行
	 * @param string $mid
	 * @return multitype:|boolean
	 */
	public function download($vid = null) {
		$this->vid = $vid;
		$this->checkParams();
		if(!empty($this->errors)) return $this->errors;
		$tmpWorkPath = $this->tmpDir . DIRECTORY_SEPARATOR . md5(microtime());
		$headerFilePath = $this->tmpDir . DIRECTORY_SEPARATOR . 'h_' . $this->vid;
		self::tp($tmpWorkPath);
		self::tp($headerFilePath);
		
		$ch = curl_init();
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
		curl_setopt($ch, CURLOPT_URL, $this->watchBaseUrl.$this->vid);
		$output = curl_exec($ch);
		
		// 動画のユニークキーストリングを取得する
		$keyStr = $this->getKeyStr($output);
		
		// 上記で取得したキーを元にAPIを叩き、XMLを取得
		$xml = @simplexml_load_string(file_get_contents($this->apiUrl.$keyStr));
		if(!$xml) return false;
		
		// 取得する動画サイズを決定する。(hd or normal)
		$videoUrl = $this->getVideoUrl($xml);
		
		curl_setopt($ch, CURLOPT_URL, $videoUrl);		
		// 動画DLセクション
		$execResult = false;
		$hdFp = fopen($headerFilePath, 'w');
		if($hdFp) {
			curl_setopt($ch, CURLOPT_WRITEHEADER, $hdFp);
			$twFp = fopen($tmpWorkPath, 'wb');
			if($twFp) {
				curl_setopt($ch, CURLOPT_RETURNTRANSFER, false);
				curl_setopt($ch, CURLOPT_FILE, $twFp);
				$execResult = curl_exec($ch);
				curl_close($ch);
				fclose($twFp);
			}
			fclose($hdFp);
		}
		if(!$execResult) goto exception;
		
		// headerFileの解析(ファイルタイプ、拡張子を取得)
		$contentType = self::parseHeaderFile($headerFilePath);
		$ext = self::getFileExtension($contentType);
		// HDファイルのダウンロードに成功していた場合、ファイル名のプレフィックスにhd_を追加
		if($this->isHd) $this->prefix .= 'hd_';
		// 実ファイルのパスを指定(お名前指定)
		$filePath = $this->saveDir . DIRECTORY_SEPARATOR . $this->prefix . $this->vid . $ext;
		// ファイル移動
		rename($tmpWorkPath, $filePath);
		
		// 作業ファイル削除
		self::u($tmpWorkPath);
		self::u($headerFilePath);
		return true;
		
		if(false) {
			exception:
			self::u($tmpWorkPath);
			self::u($headerFilePath);
			curl_close($ch);
			return false;
		}	
	}
	
	/**
	 * ファイルの存在確認と作成、及び権限の付与
	 * @param unknown $path
	 */
	protected function tp($path) {
		if(!file_exists($path)) touch($path);
		chmod($path, 0777);
	}
	
	/**
	 * ファイルの存在確認と削除
	 * @param unknown $path
	 */
	protected function u($path) {
		if(file_exists($path)) unlink($path);
	}

	/**
	 * パラメーターチェック
	 */
	protected function checkParams() {
		if(!is_dir($this->saveDir)) array_push($this->errors, 'saveDir must be a directory.');
		if(fileperms($this->saveDir) != '16895') array_push($this->errors, 'saveDir required to be permission 777.');
		if(!is_dir($this->tmpDir)) array_push($this->errors, 'tmpDir must be a directory.');
		if(fileperms($this->tmpDir) != '16895') array_push($this->errors, 'tmpDir required to be permission 777.');
		if(empty($this->vid)) array_push($this->errors, 'vid is required but not set.');
		if(!self::validateVid($this->vid)) array_push($this->errors, 'VID must be numeric.');
	}
	
	/**
	 * 動画IDの正規バリデーション
	 * @param unknown $check
	 * @return boolean
	 */
	protected function validateVid($check) {
		$regexNumOnly = '/^[0-9]+$/';
		if (preg_match($regexNumOnly, $check)) return true;
		return false;
	}
	
	/**
	 * HTMLファイルをパースして動画のユニークキーを取得
	 * @param unknown $html
	 * @return Ambigous <NULL, string>
	 */
	protected function getKeyStr($html) {
		$key = null;
		$apiPos = stripos($html, $this->apiUrl);
		if($apiPos) {
			$st = $apiPos + strlen($this->apiUrl);
			$ed = strpos($html, $this->delimiter, $st);
			$l = $ed - $st;
			$key = substr($html, $st, $l);
		}
		return $key;
	}
	
	protected function getVideoUrl($xml) {
		if(!empty($xml->filehd)) {
			$this->isHd = true;
			return (string)$xml->filehd;
		}
		if(!empty($xml->file)) return (string)$xml->file;
		return false;
	}
	
	/**
	 * ヘッダーファイルの情報を解析
	 * @param unknown $path
	 * @return Ambigous <NULL, string>
	 */
	protected function parseHeaderFile($path) {
		$hd = file_get_contents($path);
		$ct = null;
		$k = 'Content-Type:';
		$st = stripos($hd, $k);
		if($st !== false) {
			$st += strlen($k);
			$ed = strpos($hd, "\n", $st);
			if($ed === false) $ed = strlen($hd);
			$l = $ed - $st;
			$ct = strtolower(trim(substr($hd, $st, $l)));
		}
		return $ct;	
	}
	
	/**
	 * コンテントタイプより動画ファイルの拡張子を分析し返却
	 * @param unknown $ct
	 * @return string
	 */
	protected function getFileExtension($ct) {
		$e = '';
		switch($ct) {
			case 'video/3gpp':
				$e = '.3gp';
				return $e;
			case 'video/mp4':
				$e = '.mp4';
				return $e;
			case 'video/x-flv':
			default:
				$e = '.flv';
				return $e;
		}
	}
}

例の如く使い方はクラス上部のコメントアウト参照。
といいつつ一応使い方メモ。

$ani = new anitubeDownloader();
$ani->saveDir = '/path/to/saveDir';
$ani->tmpDir = '/path/to/tmpDir';
$res = $ani->download('*****');
var_dump($res);

ニコ動にしろYoutubeにしろ、これ系のクラスはやってること少しずつしか変わらないからスクレイピングの部分だけ分岐させればよいかも?

 

【jQuery】ajax()関数のラッパーメソッドまとめ。

メモ。

// GETメソッドで通信を行う。送信データ、成功時コールバック関数、レスポンスデータタイプを指定可能
get(url, data, success, dataType);

// POSTメソッドで通信を行う。引数は上記get()と同様
post(url, data, success, dataType);

// JSONデータをGETメソッドで取得する。送信データ、成功時コールバック関数を指定可能
getJSON(url, data, success);

// JavascriptをGETメソッドで取得し、実行する。成功時のコールバック関数を指定可能
getScript(url, success);

ajaxSetup()関数を用いるとajax()関数で指定可能なオプションの初期値をセットできる。
ajaxSetup()でイニシャライズを済ませておき、実際の通信はラッパー関数で行えば非常にスマートなコードにできそう。

 

【Javascript】windowオブジェクトまとめ。

めも。

// 言語情報やユーザーエージェントなどの情報が格納されている
window.navigator

// URLに関する情報が格納されている
window.location

// ブラウザ履歴を戻るback()メソッドや進めるforward()メソッドなど、履歴操作に纏わるメソッドが格納されている
window.history

// 表示している画面の幅、高さ、色数などの情報が格納されている
window.screen

// 画面内に複数のフレームが存在している場合、そのフレームへの参照が格納される
window.frames

// レンダリングされているDOMが全て格納される
window.document

たとえば。

> window.navigator.userAgent
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.101 Safari/537.36"

> window.screen.width
1440

といった具合に情報を取り出すことが出来る。

 

【Javascript】時間、回数、パフォーマンスを詳細に計測する。

めも。

console.profile()、console.profileEnd()を用いると各関数の実行回数、合計の実行時間などを計測し、コンソールへ詳細に吐き出してくれる。
上記関数にはそれぞれ同一のキーを引数として渡し、同じキーが指定されたものをペアとしてその区間の計測結果を出力してくれる。

例えば下記のようなjQueryを用いたコードのパフォーマンスを詳細に計測したい場合。

<script>
var obj = {};

obj.test = function(){
	console.log('ok');
}

$(function(){
	for(var i = 0; i < 100; i++){
		for(var j = 0; j < 10; j++){
			obj.test();
		}
	}
})
</script>

下記のようにconsole.profile()、console.profileEnd()を挿入してみる。

<script>
var obj = {};

obj.test = function(){
	console.log('ok');
}

$(function(){
	console.profile('key');
	for(var i = 0; i < 100; i++){
		for(var j = 0; j < 10; j++){
			obj.test();
		}
	}
	console.profileEnd('key');
})
</script>

そして、これを実行すると下記のようなデバッグ結果が出力される。
※下記はChromeで実行した場合のデバッグコンソール。

c7321b7fafc05912cf004164673dfe9a

パフォーマンスを極限まで追及する際に欠かせない非常に強力な機能だね。

 

【Javascript】コンソールでスタックトーレスを表示する。

めも。

console.trace();

変数のデバッグ時はconsole.log()関数を用いるが、その関数がどこから呼ばれたのかを詳細に表示したい場合は、console.trace()関数を用いるとよい。
console.trace()はトリガーになったイベントがどのオブジェクトのものであるかまで判明するため、イベントドリブン形式でプログラムを記述している場合は非常に有用。