返回 reveal.js
util.ts
根目录 / js / utils / util.ts
1 /**
2 * Extend object a with the properties of object b.
3 * If there's a conflict, object b takes precedence.
4 *
5 * @param {object} a
6 * @param {object} b
7 */
8 export const extend = (a: Record<string, any>, b: Record<string, any>) => {
9 for (let i in b) {
10 a[i] = b[i];
11 }
12
13 return a;
14 };
15
16 /**
17 * querySelectorAll but returns an Array.
18 */
19 export const queryAll = (el: Element | Document, selector: string): Element[] => {
20 return Array.from(el.querySelectorAll(selector));
21 };
22
23 /**
24 * classList.toggle() with cross browser support
25 */
26 export const toggleClass = (el: Element, className: string, value: boolean) => {
27 if (value) {
28 el.classList.add(className);
29 } else {
30 el.classList.remove(className);
31 }
32 };
33
34 type DeserializedValue = string | number | boolean | null;
35
36 /**
37 * Utility for deserializing a value.
38 *
39 * @param {*} value
40 * @return {*}
41 */
42 export const deserialize = (value: string): DeserializedValue => {
43 if (typeof value === 'string') {
44 if (value === 'null') return null;
45 else if (value === 'true') return true;
46 else if (value === 'false') return false;
47 else if (value.match(/^-?[\d\.]+$/)) return parseFloat(value);
48 }
49
50 return value;
51 };
52
53 /**
54 * Measures the distance in pixels between point a
55 * and point b.
56 *
57 * @param {object} a point with x/y properties
58 * @param {object} b point with x/y properties
59 *
60 * @return {number}
61 */
62 export const distanceBetween = (
63 a: { x: number; y: number },
64 b: { x: number; y: number }
65 ): number => {
66 let dx = a.x - b.x,
67 dy = a.y - b.y;
68
69 return Math.sqrt(dx * dx + dy * dy);
70 };
71
72 /**
73 * Applies a CSS transform to the target element.
74 *
75 * @param {HTMLElement} element
76 * @param {string} transform
77 */
78 export const transformElement = (element: HTMLElement, transform: string) => {
79 element.style.transform = transform;
80 };
81
82 /**
83 * Element.matches with IE support.
84 *
85 * @param {HTMLElement} target The element to match
86 * @param {String} selector The CSS selector to match
87 * the element against
88 *
89 * @return {Boolean}
90 */
91 export const matches = (target: any, selector: string): boolean => {
92 let matchesMethod = target.matches || target.matchesSelector || target.msMatchesSelector;
93
94 return !!(matchesMethod && matchesMethod.call(target, selector));
95 };
96
97 /**
98 * Find the closest parent that matches the given
99 * selector.
100 *
101 * @param {HTMLElement} target The child element
102 * @param {String} selector The CSS selector to match
103 * the parents against
104 *
105 * @return {HTMLElement} The matched parent or null
106 * if no matching parent was found
107 */
108 export const closest = (target: Element | null, selector: string): Element | null => {
109 // Native Element.closest
110 if (target && typeof target.closest === 'function') {
111 return target.closest(selector);
112 }
113
114 // Polyfill
115 while (target) {
116 if (matches(target, selector)) {
117 return target;
118 }
119
120 // Keep searching
121 target = target.parentElement;
122 }
123
124 return null;
125 };
126
127 /**
128 * Handling the fullscreen functionality via the fullscreen API
129 *
130 * @see http://fullscreen.spec.whatwg.org/
131 * @see https://developer.mozilla.org/en-US/docs/DOM/Using_fullscreen_mode
132 */
133 export const enterFullscreen = (element?: Element) => {
134 element = element || document.documentElement;
135
136 // Check which implementation is available
137 let requestMethod =
138 (element as any).requestFullscreen ||
139 (element as any).webkitRequestFullscreen ||
140 (element as any).webkitRequestFullScreen ||
141 (element as any).mozRequestFullScreen ||
142 (element as any).msRequestFullscreen;
143
144 if (requestMethod) {
145 requestMethod.apply(element);
146 }
147 };
148
149 /**
150 * Creates an HTML element and returns a reference to it.
151 * If the element already exists the existing instance will
152 * be returned.
153 *
154 * @param {HTMLElement} container
155 * @param {string} tagname
156 * @param {string} classname
157 * @param {string} innerHTML
158 *
159 * @return {HTMLElement}
160 */
161 export const createSingletonNode = (
162 container: Element,
163 tagname: string,
164 classname: string,
165 innerHTML: string = ''
166 ): Element => {
167 // Find all nodes matching the description
168 let nodes = container.querySelectorAll('.' + classname);
169
170 // Check all matches to find one which is a direct child of
171 // the specified container
172 for (let i = 0; i < nodes.length; i++) {
173 let testNode = nodes[i];
174 if (testNode.parentNode === container) {
175 return testNode;
176 }
177 }
178
179 // If no node was found, create it now
180 let node = document.createElement(tagname);
181 node.className = classname;
182 node.innerHTML = innerHTML;
183 container.appendChild(node);
184
185 return node;
186 };
187
188 /**
189 * Injects the given CSS styles into the DOM.
190 *
191 * @param {string} value
192 */
193 export const createStyleSheet = (value: string): HTMLStyleElement => {
194 let tag = document.createElement('style');
195
196 if (value && value.length > 0) {
197 tag.appendChild(document.createTextNode(value));
198 }
199
200 document.head.appendChild(tag);
201
202 return tag;
203 };
204
205 /**
206 * Returns a key:value hash of all query params.
207 */
208 export const getQueryHash = (): Record<string, DeserializedValue> => {
209 let query: Record<string, DeserializedValue> = {};
210
211 location.search.replace(/[A-Z0-9]+?=([\w\.%-]*)/gi, (a: string) => {
212 const key = a.split('=').shift();
213 const value = a.split('=').pop();
214 if (key && value !== undefined) {
215 query[key] = value;
216 }
217 return a;
218 });
219
220 // Basic deserialization
221 for (let i in query) {
222 let value = query[i];
223
224 query[i] = deserialize(unescape(value as string));
225 }
226
227 // Do not accept new dependencies via query config to avoid
228 // the potential of malicious script injection
229 if (typeof query['dependencies'] !== 'undefined') delete query['dependencies'];
230
231 return query;
232 };
233
234 /**
235 * Returns the remaining height within the parent of the
236 * target element.
237 *
238 * remaining height = [ configured parent height ] - [ current parent height ]
239 *
240 * @param {HTMLElement} element
241 * @param {number} [height]
242 */
243 export const getRemainingHeight = (element: HTMLElement | null, height: number = 0): number => {
244 if (element) {
245 let newHeight: number,
246 oldHeight = element.style.height;
247
248 // Change the .stretch element height to 0 in order find the height of all
249 // the other elements
250 element.style.height = '0px';
251
252 // In Overview mode, the parent (.slide) height is set of 700px.
253 // Restore it temporarily to its natural height.
254 if (element.parentElement) {
255 element.parentElement.style.height = 'auto';
256 }
257
258 newHeight = height - (element.parentElement?.offsetHeight || 0);
259
260 // Restore the old height, just in case
261 element.style.height = oldHeight + 'px';
262
263 // Clear the parent (.slide) height. .removeProperty works in IE9+
264 if (element.parentElement) {
265 element.parentElement.style.removeProperty('height');
266 }
267
268 return newHeight;
269 }
270
271 return height;
272 };
273
274 const fileExtensionToMimeMap: Record<string, string> = {
275 mp4: 'video/mp4',
276 m4a: 'video/mp4',
277 ogv: 'video/ogg',
278 mpeg: 'video/mpeg',
279 webm: 'video/webm',
280 };
281
282 /**
283 * Guess the MIME type for common file formats.
284 */
285 export const getMimeTypeFromFile = (filename: string = ''): string | undefined => {
286 const extension = filename.split('.').pop();
287 return extension ? fileExtensionToMimeMap[extension] : undefined;
288 };
289
290 /**
291 * Encodes a string for RFC3986-compliant URL format.
292 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURI#encoding_for_rfc3986
293 *
294 * @param {string} url
295 */
296 export const encodeRFC3986URI = (url: string = ''): string => {
297 return encodeURI(url)
298 .replace(/%5B/g, '[')
299 .replace(/%5D/g, ']')
300 .replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
301 };
302
302 lines TYPESCRIPT