wp-custom-header.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461
  1. /* global YT */
  2. (function( window, settings ) {
  3. var NativeHandler, YouTubeHandler;
  4. /** @namespace wp */
  5. window.wp = window.wp || {};
  6. // Fail gracefully in unsupported browsers.
  7. if ( ! ( 'addEventListener' in window ) ) {
  8. return;
  9. }
  10. /**
  11. * Trigger an event.
  12. *
  13. * @param {Element} target HTML element to dispatch the event on.
  14. * @param {string} name Event name.
  15. */
  16. function trigger( target, name ) {
  17. var evt;
  18. if ( 'function' === typeof window.Event ) {
  19. evt = new Event( name );
  20. } else {
  21. evt = document.createEvent( 'Event' );
  22. evt.initEvent( name, true, true );
  23. }
  24. target.dispatchEvent( evt );
  25. }
  26. /**
  27. * Create a custom header instance.
  28. *
  29. * @memberOf wp
  30. *
  31. * @class
  32. */
  33. function CustomHeader() {
  34. this.handlers = {
  35. nativeVideo: new NativeHandler(),
  36. youtube: new YouTubeHandler()
  37. };
  38. }
  39. CustomHeader.prototype = {
  40. /**
  41. * Initalize the custom header.
  42. *
  43. * If the environment supports video, loops through registered handlers
  44. * until one is found that can handle the video.
  45. */
  46. initialize: function() {
  47. if ( this.supportsVideo() ) {
  48. for ( var id in this.handlers ) {
  49. var handler = this.handlers[ id ];
  50. if ( 'test' in handler && handler.test( settings ) ) {
  51. this.activeHandler = handler.initialize.call( handler, settings );
  52. // Dispatch custom event when the video is loaded.
  53. trigger( document, 'wp-custom-header-video-loaded' );
  54. break;
  55. }
  56. }
  57. }
  58. },
  59. /**
  60. * Determines if the current environment supports video.
  61. *
  62. * Themes and plugins can override this method to change the criteria.
  63. *
  64. * @return {boolean}
  65. */
  66. supportsVideo: function() {
  67. // Don't load video on small screens. @todo: consider bandwidth and other factors.
  68. if ( window.innerWidth < settings.minWidth || window.innerHeight < settings.minHeight ) {
  69. return false;
  70. }
  71. return true;
  72. },
  73. /**
  74. * Base handler for custom handlers to extend.
  75. *
  76. * @type {BaseHandler}
  77. */
  78. BaseVideoHandler: BaseHandler
  79. };
  80. /**
  81. * Create a video handler instance.
  82. *
  83. * @memberOf wp
  84. *
  85. * @class
  86. */
  87. function BaseHandler() {}
  88. BaseHandler.prototype = {
  89. /**
  90. * Initialize the video handler.
  91. *
  92. * @param {object} settings Video settings.
  93. */
  94. initialize: function( settings ) {
  95. var handler = this,
  96. button = document.createElement( 'button' );
  97. this.settings = settings;
  98. this.container = document.getElementById( 'wp-custom-header' );
  99. this.button = button;
  100. button.setAttribute( 'type', 'button' );
  101. button.setAttribute( 'id', 'wp-custom-header-video-button' );
  102. button.setAttribute( 'class', 'wp-custom-header-video-button wp-custom-header-video-play' );
  103. button.innerHTML = settings.l10n.play;
  104. // Toggle video playback when the button is clicked.
  105. button.addEventListener( 'click', function() {
  106. if ( handler.isPaused() ) {
  107. handler.play();
  108. } else {
  109. handler.pause();
  110. }
  111. });
  112. // Update the button class and text when the video state changes.
  113. this.container.addEventListener( 'play', function() {
  114. button.className = 'wp-custom-header-video-button wp-custom-header-video-play';
  115. button.innerHTML = settings.l10n.pause;
  116. if ( 'a11y' in window.wp ) {
  117. window.wp.a11y.speak( settings.l10n.playSpeak);
  118. }
  119. });
  120. this.container.addEventListener( 'pause', function() {
  121. button.className = 'wp-custom-header-video-button wp-custom-header-video-pause';
  122. button.innerHTML = settings.l10n.play;
  123. if ( 'a11y' in window.wp ) {
  124. window.wp.a11y.speak( settings.l10n.pauseSpeak);
  125. }
  126. });
  127. this.ready();
  128. },
  129. /**
  130. * Ready method called after a handler is initialized.
  131. *
  132. * @abstract
  133. */
  134. ready: function() {},
  135. /**
  136. * Whether the video is paused.
  137. *
  138. * @abstract
  139. * @return {boolean}
  140. */
  141. isPaused: function() {},
  142. /**
  143. * Pause the video.
  144. *
  145. * @abstract
  146. */
  147. pause: function() {},
  148. /**
  149. * Play the video.
  150. *
  151. * @abstract
  152. */
  153. play: function() {},
  154. /**
  155. * Append a video node to the header container.
  156. *
  157. * @param {Element} node HTML element.
  158. */
  159. setVideo: function( node ) {
  160. var editShortcutNode,
  161. editShortcut = this.container.getElementsByClassName( 'customize-partial-edit-shortcut' );
  162. if ( editShortcut.length ) {
  163. editShortcutNode = this.container.removeChild( editShortcut[0] );
  164. }
  165. this.container.innerHTML = '';
  166. this.container.appendChild( node );
  167. if ( editShortcutNode ) {
  168. this.container.appendChild( editShortcutNode );
  169. }
  170. },
  171. /**
  172. * Show the video controls.
  173. *
  174. * Appends a play/pause button to header container.
  175. */
  176. showControls: function() {
  177. if ( ! this.container.contains( this.button ) ) {
  178. this.container.appendChild( this.button );
  179. }
  180. },
  181. /**
  182. * Whether the handler can process a video.
  183. *
  184. * @abstract
  185. * @param {object} settings Video settings.
  186. * @return {boolean}
  187. */
  188. test: function() {
  189. return false;
  190. },
  191. /**
  192. * Trigger an event on the header container.
  193. *
  194. * @param {string} name Event name.
  195. */
  196. trigger: function( name ) {
  197. trigger( this.container, name );
  198. }
  199. };
  200. /**
  201. * Create a custom handler.
  202. *
  203. * @memberOf wp
  204. *
  205. * @param {object} protoProps Properties to apply to the prototype.
  206. * @return CustomHandler The subclass.
  207. */
  208. BaseHandler.extend = function( protoProps ) {
  209. var prop;
  210. function CustomHandler() {
  211. var result = BaseHandler.apply( this, arguments );
  212. return result;
  213. }
  214. CustomHandler.prototype = Object.create( BaseHandler.prototype );
  215. CustomHandler.prototype.constructor = CustomHandler;
  216. for ( prop in protoProps ) {
  217. CustomHandler.prototype[ prop ] = protoProps[ prop ];
  218. }
  219. return CustomHandler;
  220. };
  221. /**
  222. * Native video handler.
  223. *
  224. * @memberOf wp
  225. *
  226. * @class
  227. */
  228. NativeHandler = BaseHandler.extend(/** @lends wp.NativeHandler.prototype */{
  229. /**
  230. * Whether the native handler supports a video.
  231. *
  232. * @param {object} settings Video settings.
  233. * @return {boolean}
  234. */
  235. test: function( settings ) {
  236. var video = document.createElement( 'video' );
  237. return video.canPlayType( settings.mimeType );
  238. },
  239. /**
  240. * Set up a native video element.
  241. */
  242. ready: function() {
  243. var handler = this,
  244. video = document.createElement( 'video' );
  245. video.id = 'wp-custom-header-video';
  246. video.autoplay = 'autoplay';
  247. video.loop = 'loop';
  248. video.muted = 'muted';
  249. video.width = this.settings.width;
  250. video.height = this.settings.height;
  251. video.addEventListener( 'play', function() {
  252. handler.trigger( 'play' );
  253. });
  254. video.addEventListener( 'pause', function() {
  255. handler.trigger( 'pause' );
  256. });
  257. video.addEventListener( 'canplay', function() {
  258. handler.showControls();
  259. });
  260. this.video = video;
  261. handler.setVideo( video );
  262. video.src = this.settings.videoUrl;
  263. },
  264. /**
  265. * Whether the video is paused.
  266. *
  267. * @return {boolean}
  268. */
  269. isPaused: function() {
  270. return this.video.paused;
  271. },
  272. /**
  273. * Pause the video.
  274. */
  275. pause: function() {
  276. this.video.pause();
  277. },
  278. /**
  279. * Play the video.
  280. */
  281. play: function() {
  282. this.video.play();
  283. }
  284. });
  285. /**
  286. * YouTube video handler.
  287. *
  288. * @memberOf wp
  289. *
  290. * @class wp.YouTubeHandler
  291. */
  292. YouTubeHandler = BaseHandler.extend(/** @lends wp.YouTubeHandler.prototype */{
  293. /**
  294. * Whether the handler supports a video.
  295. *
  296. * @param {object} settings Video settings.
  297. * @return {boolean}
  298. */
  299. test: function( settings ) {
  300. return 'video/x-youtube' === settings.mimeType;
  301. },
  302. /**
  303. * Set up a YouTube iframe.
  304. *
  305. * Loads the YouTube IFrame API if the 'YT' global doesn't exist.
  306. */
  307. ready: function() {
  308. var handler = this;
  309. if ( 'YT' in window ) {
  310. YT.ready( handler.loadVideo.bind( handler ) );
  311. } else {
  312. var tag = document.createElement( 'script' );
  313. tag.src = 'https://www.youtube.com/iframe_api';
  314. tag.onload = function () {
  315. YT.ready( handler.loadVideo.bind( handler ) );
  316. };
  317. document.getElementsByTagName( 'head' )[0].appendChild( tag );
  318. }
  319. },
  320. /**
  321. * Load a YouTube video.
  322. */
  323. loadVideo: function() {
  324. var handler = this,
  325. video = document.createElement( 'div' ),
  326. // @link http://stackoverflow.com/a/27728417
  327. VIDEO_ID_REGEX = /^.*(?:(?:youtu\.be\/|v\/|vi\/|u\/\w\/|embed\/)|(?:(?:watch)?\?v(?:i)?=|\&v(?:i)?=))([^#\&\?]*).*/;
  328. video.id = 'wp-custom-header-video';
  329. handler.setVideo( video );
  330. handler.player = new YT.Player( video, {
  331. height: this.settings.height,
  332. width: this.settings.width,
  333. videoId: this.settings.videoUrl.match( VIDEO_ID_REGEX )[1],
  334. events: {
  335. onReady: function( e ) {
  336. e.target.mute();
  337. handler.showControls();
  338. },
  339. onStateChange: function( e ) {
  340. if ( YT.PlayerState.PLAYING === e.data ) {
  341. handler.trigger( 'play' );
  342. } else if ( YT.PlayerState.PAUSED === e.data ) {
  343. handler.trigger( 'pause' );
  344. } else if ( YT.PlayerState.ENDED === e.data ) {
  345. e.target.playVideo();
  346. }
  347. }
  348. },
  349. playerVars: {
  350. autoplay: 1,
  351. controls: 0,
  352. disablekb: 1,
  353. fs: 0,
  354. iv_load_policy: 3,
  355. loop: 1,
  356. modestbranding: 1,
  357. playsinline: 1,
  358. rel: 0,
  359. showinfo: 0
  360. }
  361. });
  362. },
  363. /**
  364. * Whether the video is paused.
  365. *
  366. * @return {boolean}
  367. */
  368. isPaused: function() {
  369. return YT.PlayerState.PAUSED === this.player.getPlayerState();
  370. },
  371. /**
  372. * Pause the video.
  373. */
  374. pause: function() {
  375. this.player.pauseVideo();
  376. },
  377. /**
  378. * Play the video.
  379. */
  380. play: function() {
  381. this.player.playVideo();
  382. }
  383. });
  384. // Initialize the custom header when the DOM is ready.
  385. window.wp.customHeader = new CustomHeader();
  386. document.addEventListener( 'DOMContentLoaded', window.wp.customHeader.initialize.bind( window.wp.customHeader ), false );
  387. // Selective refresh support in the Customizer.
  388. if ( 'customize' in window.wp ) {
  389. window.wp.customize.selectiveRefresh.bind( 'render-partials-response', function( response ) {
  390. if ( 'custom_header_settings' in response ) {
  391. settings = response.custom_header_settings;
  392. }
  393. });
  394. window.wp.customize.selectiveRefresh.bind( 'partial-content-rendered', function( placement ) {
  395. if ( 'custom_header' === placement.partial.id ) {
  396. window.wp.customHeader.initialize();
  397. }
  398. });
  399. }
  400. })( window, window._wpCustomHeaderSettings || {} );