読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

自動再生する video タグを古い jQuery で動的に追加すると二重に再生される件

ちょっと実行時に video タグを動的に追加する必要があったので、以下のようなコードを組んでいたのですが、何故か二重に動画が再生されてしまう問題が発生しました。

$("#wrap").append('<video src="kamebuchi.mp4" controls autoplay loop></video>');

ちょっと調べてみると jQuery の parseHTML 実装に原因があったようなので、簡単にまとめておきます。

まずは問題が発生したバージョンと発生しなかったバージョンの parseHTML 実装を調べてみました。

jQuery 1.8.2

// data: string of html
// context (optional): If specified, the fragment will be created in this context, defaults to document
// scripts (optional): If true, will include scripts passed in the html string
parseHTML: function( data, context, scripts ) {
	var parsed;
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		scripts = context;
		context = 0;
	}
	context = context || document;

	// Single tag
	if ( (parsed = rsingleTag.exec( data )) ) {
		return [ context.createElement( parsed[1] ) ];
	}

	parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
	return jQuery.merge( [],
		(parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
},

1.8.2 の実装では buildFragment が cacheable の値として、true を返した場合にはクローンするようになっています。video タグは DOM ツリーに追加されなくても音だけは再生される仕様なので、jQuery にキャッシュされた video タグが音だけ再生していたようです。

jQuery 2.1.1

// data: string of html
// context (optional): If specified, the fragment will be created in this context, defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( !data || typeof data !== "string" ) {
		return null;
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}
	context = context || document;

	var parsed = rsingleTag.exec( data ),
		scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[1] ) ];
	}

	parsed = jQuery.buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};

ちなみに 2.1.1 の実装ではパースした結果をキャッシュする仕組みは無くなっています。

このように実装が変わったのは 1.9.0 からのようなので、新しい jQuery を使っている限りでは、二重に再生される問題は発生しないようになりました。どうしても新しい jQuery にアップデート出来ない場合には、createElement を使って昔ながらの方法で要素を作るしかなさそうです。

var video = document.createElement("video");

video.src = "kamebuchi.mp4";
video.controls = true;
video.autoplay = true;
video.loop = true;

$("#wrap").append(video);

jQuery の parseHTML を通さないので、要素がキャッシュされず問題なく再生されるようになりました。