wp-custom-header.js 10 KB

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