1/** 2 * Object Fit Videos 3 * Polyfill for object-fit and object-position CSS properties on video elements 4 * Covers IE9, IE10, IE11, Edge, Safari <10 5 * 6 * Usage 7 * In your CSS, add a special font-family tag for IE/Edge 8 * video { 9 * object-fit: cover; 10 * font-family: 'object-fit: cover;'; 11 * } 12 * 13 * Before the closing body tag, or whenever the DOM is ready, 14 * make the JavaScript call 15 * objectFitVideos(); 16 * 17 * All video elements with the special CSS property will be targeted 18 * 19 * @license MIT (https://opensource.org/licenses/MIT) 20 * @author Todd Miller <[email protected]> 21 * @version 1.0.2 22 * @changelog 23 * 2016-08-19 - Adds object-position support. 24 * 2016-08-19 - Add throttle function for more performant resize events 25 * 2016-08-19 - Initial release with object-fit support, and 26 * object-position default 'center' 27 * 2016-10-14 - No longer relies on window load event, instead requires a specific 28 * function call to initialize the videos for object fit and position. 29 * 2016-11-28 - Support CommonJS environment, courtesy of @msorensson 30 * 2016-12-05 - Refactors the throttling function to support IE 31 * 2017-09-26 - Fix an issue with autplay not working on polyfilled videos 32 * - Adds the capability to specify elements to polyfill, 33 * instead of just checking every video element for the 34 * CSS property. Slight performance gain in most usecases, 35 * and a bigger gain in a few usecases. 36 * 2017-10-24 - Add user agent check to enable polyfill for all Edge browsers. 37 * object-fit is supported on Edge >= 16, but currently just for images. 38 */ 39var objectFitVideos = function (videos) { 40 'use strict'; 41 42 var isEdge = navigator.userAgent.indexOf('Edge/') >= 0; 43 44 var testImg = new Image(), 45 supportsObjectFit = 'object-fit' in testImg.style && !isEdge, 46 supportsObjectPosition = 'object-position' in testImg.style && !isEdge, 47 propRegex = /(object-fit|object-position)\s*:\s*([-\w\s%]+)/g; 48 49 if (!supportsObjectFit || !supportsObjectPosition) { 50 initialize(videos); 51 throttle('resize', 'optimizedResize'); 52 } 53 54 /** 55 * Parse the style and look for the special font-family tag 56 * @param {object} $el The element to parse 57 * @return {object} The font-family properties we're interested in 58 */ 59 function getStyle ($el) { 60 var style = getComputedStyle($el).fontFamily, 61 parsed = null, 62 props = {}; 63 64 while ((parsed = propRegex.exec(style)) !== null) { 65 props[parsed[1]] = parsed[2]; 66 } 67 68 if (props['object-position']) 69 return parsePosition(props); 70 71 return props; 72 } 73 74 /** 75 * Initialize all the relevant video elements and get them fitted 76 */ 77 function initialize (videos) { 78 var index = -1; 79 80 if (!videos) { 81 // if no videos given, query all video elements 82 videos = document.querySelectorAll('video'); 83 } else if (!('length' in videos)) { 84 // convert to an array for proper looping if an array or NodeList 85 // was not given 86 videos = [videos]; 87 } 88 89 while (videos[++index]) { 90 var style = getStyle(videos[index]); 91 92 // only do work if the property is on the element 93 if (style['object-fit'] || style['object-position']) { 94 // set the default values 95 style['object-fit'] = style['object-fit'] || 'fill'; 96 fitIt(videos[index], style); 97 } 98 } 99 } 100 101 /** 102 * Object Fit 103 * @param {object} $el Element to fit 104 * @return {object} The element's relevant properties 105 */ 106 function fitIt ($el, style) { 107 // fill is the default behavior, no action is necessary 108 if (style['object-fit'] === 'fill') 109 return; 110 111 // convenience style properties on the source element 112 var setCss = $el.style, 113 getCss = window.getComputedStyle($el); 114 115 // create and insert a wrapper element 116 var $wrap = document.createElement('object-fit'); 117 $wrap.appendChild($el.parentNode.replaceChild($wrap, $el)); 118 119 // style the wrapper element to mostly match the source element 120 var wrapCss = { 121 height: '100%', 122 width: '100%', 123 boxSizing: 'content-box', 124 display: 'inline-block', 125 overflow: 'hidden' 126 }; 127 128 'backgroundColor backgroundImage borderColor borderStyle borderWidth bottom fontSize lineHeight left op
128acity margin position right top visibility'.replace(/\w+/g, function (key) { 129 wrapCss[key] = getCss[key]; 130 }); 131 132 for (var key in wrapCss) 133 $wrap.style[key] = wrapCss[key]; 134 135 // give the source element some saner styles 136 setCss.border = setCss.margin = setCss.padding = 0; 137 setCss.display = 'block'; 138 setCss.opacity = 1; 139 140 // set up the event handlers 141 $el.addEventListener('loadedmetadata', doWork); 142 window.addEventListener('optimizedResize', doWork); 143 144 // we may have missed the loadedmetadata event, so if the video has loaded 145 // enough data, just drop the event listener and execute 146 if ($el.readyState >= 1) { 147 $el.removeEventListener('loadedmetadata', doWork); 148 doWork(); 149 } 150 151 /** 152 * Do the actual sizing. Math. 153 * @methodOf fitIt 154 */ 155 function doWork () { 156 // the actual size and ratio of the video 157 // we do this here, even though it doesn't change, because 158 // at this point we can be sure the metadata has loaded 159 var videoWidth = $el.videoWidth, 160 videoHeight = $el.videoHeight, 161 videoRatio = videoWidth / videoHeight; 162 163 var wrapWidth = $wrap.clientWidth, 164 wrapHeight = $wrap.clientHeight, 165 wrapRatio = wrapWidth / wrapHeight; 166 167 var newHeight = 0, 168 newWidth = 0; 169 setCss.marginLeft = setCss.marginTop = 0; 170 171 // basically we do the opposite action for contain and cover, 172 // depending on whether the video aspect ratio is less than or 173 // greater than the wrapper's aspect ratio 174 if (videoRatio < wrapRatio ? 175 style['object-fit'] === 'contain' : style['object-fit'] === 'cover') { 176 newHeight = wrapHeight * videoRatio; 177 newWidth = wrapWidth / videoRatio; 178 179 setCss.width = Math.round(newHeight) + 'px'; 180 setCss.height = wrapHeight + 'px'; 181 182 if (style['object-position-x'] === 'left') 183 setCss.marginLeft = 0; 184 else if (style['object-position-x'] === 'right') 185 setCss.marginLeft = Math.round(wrapWidth - newHeight) + 'px'; 186 else 187 setCss.marginLeft = Math.round((wrapWidth - newHeight) / 2) + 'px'; 188 } else { 189 newWidth = wrapWidth / videoRatio; 190 191 setCss.width = wrapWidth + 'px'; 192 setCss.height = Math.round(newWidth) + 'px'; 193 194 if (style['object-position-y'] === 'top') 195 setCss.marginTop = 0; 196 else if (style['object-position-y'] === 'bottom') 197 setCss.marginTop = Math.round(wrapHeight - newWidth) + 'px'; 198 else 199 setCss.marginTop = Math.round((wrapHeight - newWidth) / 2) + 'px'; 200 } 201 202 // play the video if autoplay is set 203 if ($el.autoplay) 204 $el.play(); 205 } 206 } 207 208 /** 209 * Split the object-position property into x and y position properties 210 * @param {object} style Relevant element styles 211 * @return {object} The style object with the added x and y props 212 */ 213 function parsePosition (style) { 214 if (~style['object-position'].indexOf('left')) 215 style['object-position-x'] = 'left'; 216 else if (~style['object-position'].indexOf('right')) 217 style['object-position-x'] = 'right'; 218 else 219 style['object-position-x'] = 'center'; 220 221 if (~style['object-position'].indexOf('top')) 222 style['object-position-y'] = 'top'; 223 else if (~style['object-position'].indexOf('bottom')) 224 style['object-position-y'] = 'bottom'; 225 else 226 style['object-position-y'] = 'center'; 227 228 return style; 229 } 230 231 /** 232 * Throttle an event with RequestAnimationFrame API for better performance 233 * @param {string} type The event to throttle 234 * @param {string} name Custom event name to listen for 235 * @param {object} obj Optional object to attach the event to 236 */ 237 function throttle (type, name, obj) { 238 obj = obj || window; 239 var running = false, 240 evt = null; 241 242 // IE does not support the CustomEvent constructor 243 // so if that fails do it the old way 244 try { 245 evt = new CustomEvent(name); 246 } catch (e) { 247 evt = document.createEvent('Event'); 248 evt.initEvent(name, true, true); 249 } 250 251 var func = function () { 252 if (running) return; 253 254 running = true; 255 requestAnimationFrame(function () { 256 obj.dispatchEvent(evt); 257 running = false;
258 }); 259 }; 260 261 obj.addEventListener(type, func); 262 } 263}; 264 265if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') 266 module.exports = objectFitVideos;
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.