90 | )}
91 |
92 |
93 |
94 |
95 |
96 | );
97 |
98 | export default Sticky2;
99 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-scrollmagic
2 |
3 | > React components for ScrollMagic
4 |
5 | [](https://www.npmjs.com/package/react-scrollmagic)
6 |
7 | :warning: **This library is not developed any further right now. Please consider to use GSAP ScrollTrigger instead. I have a helper component in my other library react-gsap: https://bitworking.github.io/react-gsap/src-components-scroll-trigger**
8 |
9 | # Introduction
10 |
11 | react-scrollmagic lets you use the [ScrollMagic](http://scrollmagic.io/) library in React in a fully declarative way. It abstracts away the direct use of the ScrollMagic classes [ScrollMagic.Controller](http://scrollmagic.io/docs/ScrollMagic.Controller.html) and [ScrollMagic.Scene](http://scrollmagic.io/docs/ScrollMagic.Scene.html).
12 |
13 | From version 2 on the GSAP library in no more included. But react-scrollmagic plays nicely together with [react-gsap](https://github.com/bitworking/react-gsap).
14 |
15 | ## Install
16 |
17 | ```bash
18 | npm install --save react-scrollmagic
19 | ```
20 |
21 | ## Usage
22 |
23 | ```jsx
24 | import React from 'react';
25 | import { Controller, Scene } from 'react-scrollmagic';
26 |
27 | const App = () => (
28 |
;
77 | }
78 | return child;
79 | }
80 |
81 | const callChildFunction = (children, progress, event) => {
82 | if (children && typeof children === 'function') {
83 | return children(progress, event);
84 | }
85 | return children;
86 | }
87 |
88 | const getChild = (children, progress, event) => {
89 | children = controlGSAP(children, progress, event);
90 | children = callChildFunction(children, progress, event);
91 | return React.Children.only(children);
92 | }
93 |
94 | const isString = (element) => {
95 | if (typeof element === 'string' || element instanceof String) {
96 | return true;
97 | }
98 | return false;
99 | }
100 |
101 | class SceneBase extends React.PureComponent {
102 | ref: HTMLElement;
103 | scene: any;
104 | child: any;
105 | state: SceneBaseState = {
106 | event: 'init',
107 | progress: 0,
108 | }
109 |
110 | componentDidMount() {
111 | const {
112 | children,
113 | controller,
114 | classToggle,
115 | pin,
116 | pinSettings,
117 | indicators,
118 | enabled,
119 | ...sceneParams
120 | } = this.props;
121 |
122 | const element = this.ref;
123 | sceneParams.triggerElement = sceneParams.triggerElement === null ? null : sceneParams.triggerElement || element;
124 |
125 | this.scene = new ScrollMagic.Scene(sceneParams);
126 |
127 | this.initEventHandlers();
128 |
129 | if (classToggle) {
130 | this.setClassToggle(this.scene, element, classToggle);
131 | }
132 |
133 | if (pin || pinSettings) {
134 | this.setPin(this.scene, element, pin, pinSettings);
135 | }
136 |
137 | if (indicators) {
138 | this.scene.addIndicators({ name: ' ' });
139 | }
140 |
141 | if (enabled !== undefined) {
142 | this.scene.enabled(enabled);
143 | }
144 |
145 | this.scene.addTo(controller);
146 | }
147 |
148 | componentDidUpdate(prevProps: SceneBaseProps) {
149 | const {
150 | duration,
151 | offset,
152 | triggerElement,
153 | triggerHook,
154 | reverse,
155 | enabled,
156 | } = this.props;
157 |
158 | if (duration !== undefined && duration !== prevProps.duration) {
159 | this.scene.duration(duration);
160 | }
161 |
162 | if (offset !== undefined && offset !== prevProps.offset) {
163 | this.scene.offset(offset);
164 | }
165 |
166 | if (triggerElement !== undefined && triggerElement !== prevProps.triggerElement) {
167 | // this.scene.triggerElement(triggerElement);
168 | }
169 |
170 | if (triggerHook !== undefined && triggerHook !== prevProps.triggerHook) {
171 | this.scene.triggerHook(triggerHook);
172 | }
173 |
174 | if (reverse !== undefined && reverse !== prevProps.reverse) {
175 | this.scene.reverse(reverse);
176 | }
177 |
178 | if (enabled !== undefined && enabled !== prevProps.enabled) {
179 | this.scene.enabled(enabled);
180 | }
181 | }
182 |
183 | componentWillUnmount() {
184 | this.scene.destroy();
185 | }
186 |
187 | setClassToggle(scene, element, classToggle) {
188 | if (Array.isArray(classToggle) && classToggle.length === 2) {
189 | scene.setClassToggle(classToggle[0], classToggle[1]);
190 | }
191 | else {
192 | scene.setClassToggle(element, classToggle);
193 | }
194 | }
195 |
196 | setPin(scene, element, pin, pinSettings) {
197 | element = isString(pin) ? pin : element;
198 | scene.setPin(element, pinSettings);
199 | }
200 |
201 | initEventHandlers() {
202 | let { children, progressEvents = true } = this.props;
203 |
204 | if (typeof children !== 'function' && !isGSAP(callChildFunction(children, 0, 'init'))) {
205 | return;
206 | }
207 |
208 | this.scene.on('start end enter leave', (event) => {
209 | this.setState({
210 | event
211 | });
212 | });
213 |
214 | if(progressEvents){
215 | this.scene.on('progress', (event) => {
216 | this.setState({
217 | progress: event.progress
218 | });
219 | });
220 | }
221 | }
222 |
223 | render() {
224 | let { children } = this.props;
225 | const { progress, event } = this.state;
226 |
227 | const child = getChild(children, progress, event);
228 |
229 | // TODO: Don't add ref to stateless or stateful components
230 |
231 | return React.cloneElement(child, { [refOrInnerRef(child)]: ref => this.ref = ref });
232 | }
233 | }
234 |
235 | class SceneWrapper extends React.PureComponent {
236 | static displayName = 'Scene';
237 |
238 | render() {
239 | if (!this.props.controller) {
240 | let { children } = this.props;
241 | const progress = 0;
242 | const event = 'init';
243 |
244 | return getChild(children, progress, event);
245 | }
246 |
247 | return (
248 |
249 | );
250 | }
251 | }
252 |
253 | export const Scene = ({ children, ...props }) => (
254 |
255 | {controller => (
256 |
257 | {children}
258 |
259 | )}
260 |
261 | );
--------------------------------------------------------------------------------
/src/lib/debug.addIndicators.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * ScrollMagic v2.0.7 (2019-05-07)
3 | * The javascript library for magical scroll interactions.
4 | * (c) 2019 Jan Paepke (@janpaepke)
5 | * Project Website: http://scrollmagic.io
6 | *
7 | * @version 2.0.7
8 | * @license Dual licensed under MIT license and GPL.
9 | * @author Jan Paepke - e-mail@janpaepke.de
10 | *
11 | * @file Debug Extension for ScrollMagic.
12 | */
13 | /**
14 | * This plugin was formerly known as the ScrollMagic debug extension.
15 | *
16 | * It enables you to add visual indicators to your page, to be able to see exactly when a scene is triggered.
17 | *
18 | * To have access to this extension, please include `plugins/debug.addIndicators.js`.
19 | * @mixin debug.addIndicators
20 | */
21 | export default function (ScrollMagic) {
22 | var NAMESPACE = "debug.addIndicators";
23 |
24 | if (typeof window === 'undefined') {
25 | var window = {};
26 | window.addEventListener = function(){};
27 | }
28 |
29 | var
30 | console = window.console || {},
31 | err = Function.prototype.bind.call(console.error || console.log || function () {}, console);
32 | if (!ScrollMagic) {
33 | err("(" + NAMESPACE + ") -> ERROR: The ScrollMagic main module could not be found. Please make sure it's loaded before this plugin or use an asynchronous loader like requirejs.");
34 | }
35 |
36 | // plugin settings
37 | var
38 | FONT_SIZE = "0.85em",
39 | ZINDEX = "9999",
40 | EDGE_OFFSET = 15; // minimum edge distance, added to indentation
41 |
42 | // overall vars
43 | var
44 | _util = ScrollMagic._util,
45 | _autoindex = 0;
46 |
47 |
48 |
49 | ScrollMagic.Scene.extend(function () {
50 | var
51 | Scene = this,
52 | _indicator;
53 |
54 | var log = function () {
55 | if (Scene._log) { // not available, when main source minified
56 | Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ")", "->");
57 | Scene._log.apply(this, arguments);
58 | }
59 | };
60 |
61 | /**
62 | * Add visual indicators for a ScrollMagic.Scene.
63 | * @memberof! debug.addIndicators#
64 | *
65 | * @example
66 | * // add basic indicators
67 | * scene.addIndicators()
68 | *
69 | * // passing options
70 | * scene.addIndicators({name: "pin scene", colorEnd: "#FFFFFF"});
71 | *
72 | * @param {object} [options] - An object containing one or more options for the indicators.
73 | * @param {(string|object)} [options.parent] - A selector, DOM Object or a jQuery object that the indicators should be added to.
74 | If undefined, the controller's container will be used.
75 | * @param {number} [options.name=""] - This string will be displayed at the start and end indicators of the scene for identification purposes. If no name is supplied an automatic index will be used.
76 | * @param {number} [options.indent=0] - Additional position offset for the indicators (useful, when having multiple scenes starting at the same position).
77 | * @param {string} [options.colorStart=green] - CSS color definition for the start indicator.
78 | * @param {string} [options.colorEnd=red] - CSS color definition for the end indicator.
79 | * @param {string} [options.colorTrigger=blue] - CSS color definition for the trigger indicator.
80 | */
81 | Scene.addIndicators = function (options) {
82 | if (!_indicator) {
83 | var
84 | DEFAULT_OPTIONS = {
85 | name: "",
86 | indent: 0,
87 | parent: undefined,
88 | colorStart: "green",
89 | colorEnd: "red",
90 | colorTrigger: "blue",
91 | };
92 |
93 | options = _util.extend({}, DEFAULT_OPTIONS, options);
94 |
95 | _autoindex++;
96 | _indicator = new Indicator(Scene, options);
97 |
98 | Scene.on("add.plugin_addIndicators", _indicator.add);
99 | Scene.on("remove.plugin_addIndicators", _indicator.remove);
100 | Scene.on("destroy.plugin_addIndicators", Scene.removeIndicators);
101 |
102 | // it the scene already has a controller we can start right away.
103 | if (Scene.controller()) {
104 | _indicator.add();
105 | }
106 | }
107 | return Scene;
108 | };
109 |
110 | /**
111 | * Removes visual indicators from a ScrollMagic.Scene.
112 | * @memberof! debug.addIndicators#
113 | *
114 | * @example
115 | * // remove previously added indicators
116 | * scene.removeIndicators()
117 | *
118 | */
119 | Scene.removeIndicators = function () {
120 | if (_indicator) {
121 | _indicator.remove();
122 | this.off("*.plugin_addIndicators");
123 | _indicator = undefined;
124 | }
125 | return Scene;
126 | };
127 |
128 | });
129 |
130 |
131 | /*
132 | * ----------------------------------------------------------------
133 | * Extension for controller to store and update related indicators
134 | * ----------------------------------------------------------------
135 | */
136 | // add option to globally auto-add indicators to scenes
137 | /**
138 | * Every ScrollMagic.Controller instance now accepts an additional option.
139 | * See {@link ScrollMagic.Controller} for a complete list of the standard options.
140 | * @memberof! debug.addIndicators#
141 | * @method new ScrollMagic.Controller(options)
142 | * @example
143 | * // make a controller and add indicators to all scenes attached
144 | * var controller = new ScrollMagic.Controller({addIndicators: true});
145 | * // this scene will automatically have indicators added to it
146 | * new ScrollMagic.Scene()
147 | * .addTo(controller);
148 | *
149 | * @param {object} [options] - Options for the Controller.
150 | * @param {boolean} [options.addIndicators=false] - If set to `true` every scene that is added to the controller will automatically get indicators added to it.
151 | */
152 | ScrollMagic.Controller.addOption("addIndicators", false);
153 | // extend Controller
154 | ScrollMagic.Controller.extend(function () {
155 | var
156 | Controller = this,
157 | _info = Controller.info(),
158 | _container = _info.container,
159 | _isDocument = _info.isDocument,
160 | _vertical = _info.vertical,
161 | _indicators = { // container for all indicators and methods
162 | groups: []
163 | };
164 |
165 | var log = function () {
166 | if (Controller._log) { // not available, when main source minified
167 | Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ")", "->");
168 | Controller._log.apply(this, arguments);
169 | }
170 | };
171 | if (Controller._indicators) {
172 | log(2, "WARNING: Scene already has a property '_indicators', which will be overwritten by plugin.");
173 | }
174 |
175 | // add indicators container
176 | this._indicators = _indicators;
177 | /*
178 | needed updates:
179 | +++++++++++++++
180 | start/end position on scene shift (handled in Indicator class)
181 | trigger parameters on triggerHook value change (handled in Indicator class)
182 | bounds position on container scroll or resize (to keep alignment to bottom/right)
183 | trigger position on container resize, window resize (if container isn't document) and window scroll (if container isn't document)
184 | */
185 |
186 | // event handler for when associated bounds markers need to be repositioned
187 | var handleBoundsPositionChange = function () {
188 | _indicators.updateBoundsPositions();
189 | };
190 |
191 | // event handler for when associated trigger groups need to be repositioned
192 | var handleTriggerPositionChange = function () {
193 | _indicators.updateTriggerGroupPositions();
194 | };
195 |
196 | _container.addEventListener("resize", handleTriggerPositionChange);
197 | if (!_isDocument) {
198 | window.addEventListener("resize", handleTriggerPositionChange);
199 | window.addEventListener("scroll", handleTriggerPositionChange);
200 | }
201 | // update all related bounds containers
202 | _container.addEventListener("resize", handleBoundsPositionChange);
203 | _container.addEventListener("scroll", handleBoundsPositionChange);
204 |
205 |
206 | // updates the position of the bounds container to aligned to the right for vertical containers and to the bottom for horizontal
207 | this._indicators.updateBoundsPositions = function (specificIndicator) {
208 | var // constant for all bounds
209 | groups = specificIndicator ? [_util.extend({}, specificIndicator.triggerGroup, {
210 | members: [specificIndicator]
211 | })] : // create a group with only one element
212 | _indicators.groups, // use all
213 | g = groups.length,
214 | css = {},
215 | paramPos = _vertical ? "left" : "top",
216 | paramDimension = _vertical ? "width" : "height",
217 | edge = _vertical ?
218 | _util.get.scrollLeft(_container) + _util.get.width(_container) - EDGE_OFFSET :
219 | _util.get.scrollTop(_container) + _util.get.height(_container) - EDGE_OFFSET,
220 | b, triggerSize, group;
221 | while (g--) { // group loop
222 | group = groups[g];
223 | b = group.members.length;
224 | triggerSize = _util.get[paramDimension](group.element.firstChild);
225 | while (b--) { // indicators loop
226 | css[paramPos] = edge - triggerSize;
227 | _util.css(group.members[b].bounds, css);
228 | }
229 | }
230 | };
231 |
232 | // updates the positions of all trigger groups attached to a controller or a specific one, if provided
233 | this._indicators.updateTriggerGroupPositions = function (specificGroup) {
234 | var // constant vars
235 | groups = specificGroup ? [specificGroup] : _indicators.groups,
236 | i = groups.length,
237 | container = _isDocument ? document.body : _container,
238 | containerOffset = _isDocument ? {
239 | top: 0,
240 | left: 0
241 | } : _util.get.offset(container, true),
242 | edge = _vertical ?
243 | _util.get.width(_container) - EDGE_OFFSET :
244 | _util.get.height(_container) - EDGE_OFFSET,
245 | paramDimension = _vertical ? "width" : "height",
246 | paramTransform = _vertical ? "Y" : "X";
247 | var // changing vars
248 | group,
249 | elem,
250 | pos,
251 | elemSize,
252 | transform;
253 | while (i--) {
254 | group = groups[i];
255 | elem = group.element;
256 | pos = group.triggerHook * Controller.info("size");
257 | elemSize = _util.get[paramDimension](elem.firstChild.firstChild);
258 | transform = pos > elemSize ? "translate" + paramTransform + "(-100%)" : "";
259 |
260 | _util.css(elem, {
261 | top: containerOffset.top + (_vertical ? pos : edge - group.members[0].options.indent),
262 | left: containerOffset.left + (_vertical ? edge - group.members[0].options.indent : pos)
263 | });
264 | _util.css(elem.firstChild.firstChild, {
265 | "-ms-transform": transform,
266 | "-webkit-transform": transform,
267 | "transform": transform
268 | });
269 | }
270 | };
271 |
272 | // updates the label for the group to contain the name, if it only has one member
273 | this._indicators.updateTriggerGroupLabel = function (group) {
274 | var
275 | text = "trigger" + (group.members.length > 1 ? "" : " " + group.members[0].options.name),
276 | elem = group.element.firstChild.firstChild,
277 | doUpdate = elem.textContent !== text;
278 | if (doUpdate) {
279 | elem.textContent = text;
280 | if (_vertical) { // bounds position is dependent on text length, so update
281 | _indicators.updateBoundsPositions();
282 | }
283 | }
284 | };
285 |
286 | // add indicators if global option is set
287 | this.addScene = function (newScene) {
288 |
289 | if (this._options.addIndicators && newScene instanceof ScrollMagic.Scene && newScene.controller() === Controller) {
290 | newScene.addIndicators();
291 | }
292 | // call original destroy method
293 | this.$super.addScene.apply(this, arguments);
294 | };
295 |
296 | // remove all previously set listeners on destroy
297 | this.destroy = function () {
298 | _container.removeEventListener("resize", handleTriggerPositionChange);
299 | if (!_isDocument) {
300 | window.removeEventListener("resize", handleTriggerPositionChange);
301 | window.removeEventListener("scroll", handleTriggerPositionChange);
302 | }
303 | _container.removeEventListener("resize", handleBoundsPositionChange);
304 | _container.removeEventListener("scroll", handleBoundsPositionChange);
305 | // call original destroy method
306 | this.$super.destroy.apply(this, arguments);
307 | };
308 | return Controller;
309 |
310 | });
311 |
312 | /*
313 | * ----------------------------------------------------------------
314 | * Internal class for the construction of Indicators
315 | * ----------------------------------------------------------------
316 | */
317 | var Indicator = function (Scene, options) {
318 | var
319 | Indicator = this,
320 | _elemBounds = TPL.bounds(),
321 | _elemStart = TPL.start(options.colorStart),
322 | _elemEnd = TPL.end(options.colorEnd),
323 | _boundsContainer = options.parent && _util.get.elements(options.parent)[0],
324 | _vertical,
325 | _ctrl;
326 |
327 | var log = function () {
328 | if (Scene._log) { // not available, when main source minified
329 | Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ")", "->");
330 | Scene._log.apply(this, arguments);
331 | }
332 | };
333 |
334 | options.name = options.name || _autoindex;
335 |
336 | // prepare bounds elements
337 | _elemStart.firstChild.textContent += " " + options.name;
338 | _elemEnd.textContent += " " + options.name;
339 | _elemBounds.appendChild(_elemStart);
340 | _elemBounds.appendChild(_elemEnd);
341 |
342 | // set public variables
343 | Indicator.options = options;
344 | Indicator.bounds = _elemBounds;
345 | // will be set later
346 | Indicator.triggerGroup = undefined;
347 |
348 | // add indicators to DOM
349 | this.add = function () {
350 | _ctrl = Scene.controller();
351 | _vertical = _ctrl.info("vertical");
352 |
353 | var isDocument = _ctrl.info("isDocument");
354 |
355 | if (!_boundsContainer) {
356 | // no parent supplied or doesnt exist
357 | _boundsContainer = isDocument ? document.body : _ctrl.info("container"); // check if window/document (then use body)
358 | }
359 | if (!isDocument && _util.css(_boundsContainer, "position") === 'static') {
360 | // position mode needed for correct positioning of indicators
361 | _util.css(_boundsContainer, {
362 | position: "relative"
363 | });
364 | }
365 |
366 | // add listeners for updates
367 | Scene.on("change.plugin_addIndicators", handleTriggerParamsChange);
368 | Scene.on("shift.plugin_addIndicators", handleBoundsParamsChange);
369 |
370 | // updates trigger & bounds (will add elements if needed)
371 | updateTriggerGroup();
372 | updateBounds();
373 |
374 | setTimeout(function () { // do after all execution is finished otherwise sometimes size calculations are off
375 | _ctrl._indicators.updateBoundsPositions(Indicator);
376 | }, 0);
377 |
378 | log(3, "added indicators");
379 | };
380 |
381 | // remove indicators from DOM
382 | this.remove = function () {
383 | if (Indicator.triggerGroup) { // if not set there's nothing to remove
384 | Scene.off("change.plugin_addIndicators", handleTriggerParamsChange);
385 | Scene.off("shift.plugin_addIndicators", handleBoundsParamsChange);
386 |
387 | if (Indicator.triggerGroup.members.length > 1) {
388 | // just remove from memberlist of old group
389 | var group = Indicator.triggerGroup;
390 | group.members.splice(group.members.indexOf(Indicator), 1);
391 | _ctrl._indicators.updateTriggerGroupLabel(group);
392 | _ctrl._indicators.updateTriggerGroupPositions(group);
393 | Indicator.triggerGroup = undefined;
394 | } else {
395 | // remove complete group
396 | removeTriggerGroup();
397 | }
398 | removeBounds();
399 |
400 | log(3, "removed indicators");
401 | }
402 | };
403 |
404 | /*
405 | * ----------------------------------------------------------------
406 | * internal Event Handlers
407 | * ----------------------------------------------------------------
408 | */
409 |
410 | // event handler for when bounds params change
411 | var handleBoundsParamsChange = function () {
412 | updateBounds();
413 | };
414 |
415 | // event handler for when trigger params change
416 | var handleTriggerParamsChange = function (e) {
417 | if (e.what === "triggerHook") {
418 | updateTriggerGroup();
419 | }
420 | };
421 |
422 | /*
423 | * ----------------------------------------------------------------
424 | * Bounds (start / stop) management
425 | * ----------------------------------------------------------------
426 | */
427 |
428 | // adds an new bounds elements to the array and to the DOM
429 | var addBounds = function () {
430 | var v = _ctrl.info("vertical");
431 | // apply stuff we didn't know before...
432 | _util.css(_elemStart.firstChild, {
433 | "border-bottom-width": v ? 1 : 0,
434 | "border-right-width": v ? 0 : 1,
435 | "bottom": v ? -1 : options.indent,
436 | "right": v ? options.indent : -1,
437 | "padding": v ? "0 8px" : "2px 4px",
438 | });
439 | _util.css(_elemEnd, {
440 | "border-top-width": v ? 1 : 0,
441 | "border-left-width": v ? 0 : 1,
442 | "top": v ? "100%" : "",
443 | "right": v ? options.indent : "",
444 | "bottom": v ? "" : options.indent,
445 | "left": v ? "" : "100%",
446 | "padding": v ? "0 8px" : "2px 4px"
447 | });
448 | // append
449 | _boundsContainer.appendChild(_elemBounds);
450 | };
451 |
452 | // remove bounds from list and DOM
453 | var removeBounds = function () {
454 | _elemBounds.parentNode.removeChild(_elemBounds);
455 | };
456 |
457 | // update the start and end positions of the scene
458 | var updateBounds = function () {
459 | if (_elemBounds.parentNode !== _boundsContainer) {
460 | addBounds(); // Add Bounds elements (start/end)
461 | }
462 | var css = {};
463 | css[_vertical ? "top" : "left"] = Scene.triggerPosition();
464 | css[_vertical ? "height" : "width"] = Scene.duration();
465 | _util.css(_elemBounds, css);
466 | _util.css(_elemEnd, {
467 | display: Scene.duration() > 0 ? "" : "none"
468 | });
469 | };
470 |
471 | /*
472 | * ----------------------------------------------------------------
473 | * trigger and trigger group management
474 | * ----------------------------------------------------------------
475 | */
476 |
477 | // adds an new trigger group to the array and to the DOM
478 | var addTriggerGroup = function () {
479 | var triggerElem = TPL.trigger(options.colorTrigger); // new trigger element
480 | var css = {};
481 | css[_vertical ? "right" : "bottom"] = 0;
482 | css[_vertical ? "border-top-width" : "border-left-width"] = 1;
483 | _util.css(triggerElem.firstChild, css);
484 | _util.css(triggerElem.firstChild.firstChild, {
485 | padding: _vertical ? "0 8px 3px 8px" : "3px 4px"
486 | });
487 | document.body.appendChild(triggerElem); // directly add to body
488 | var newGroup = {
489 | triggerHook: Scene.triggerHook(),
490 | element: triggerElem,
491 | members: [Indicator]
492 | };
493 | _ctrl._indicators.groups.push(newGroup);
494 | Indicator.triggerGroup = newGroup;
495 | // update right away
496 | _ctrl._indicators.updateTriggerGroupLabel(newGroup);
497 | _ctrl._indicators.updateTriggerGroupPositions(newGroup);
498 | };
499 |
500 | var removeTriggerGroup = function () {
501 | _ctrl._indicators.groups.splice(_ctrl._indicators.groups.indexOf(Indicator.triggerGroup), 1);
502 | Indicator.triggerGroup.element.parentNode.removeChild(Indicator.triggerGroup.element);
503 | Indicator.triggerGroup = undefined;
504 | };
505 |
506 | // updates the trigger group -> either join existing or add new one
507 | /*
508 | * Logic:
509 | * 1 if a trigger group exist, check if it's in sync with Scene settings – if so, nothing else needs to happen
510 | * 2 try to find an existing one that matches Scene parameters
511 | * 2.1 If a match is found check if already assigned to an existing group
512 | * If so:
513 | * A: it was the last member of existing group -> kill whole group
514 | * B: the existing group has other members -> just remove from member list
515 | * 2.2 Assign to matching group
516 | * 3 if no new match could be found, check if assigned to existing group
517 | * A: yes, and it's the only member -> just update parameters and positions and keep using this group
518 | * B: yes but there are other members -> remove from member list and create a new one
519 | * C: no, so create a new one
520 | */
521 | var updateTriggerGroup = function () {
522 | var
523 | triggerHook = Scene.triggerHook(),
524 | closeEnough = 0.0001;
525 |
526 | // Have a group, check if it still matches
527 | if (Indicator.triggerGroup) {
528 | if (Math.abs(Indicator.triggerGroup.triggerHook - triggerHook) < closeEnough) {
529 | // _util.log(0, "trigger", options.name, "->", "no need to change, still in sync");
530 | return; // all good
531 | }
532 | }
533 | // Don't have a group, check if a matching one exists
534 | // _util.log(0, "trigger", options.name, "->", "out of sync!");
535 | var
536 | groups = _ctrl._indicators.groups,
537 | group,
538 | i = groups.length;
539 | while (i--) {
540 | group = groups[i];
541 | if (Math.abs(group.triggerHook - triggerHook) < closeEnough) {
542 | // found a match!
543 | // _util.log(0, "trigger", options.name, "->", "found match");
544 | if (Indicator.triggerGroup) { // do I have an old group that is out of sync?
545 | if (Indicator.triggerGroup.members.length === 1) { // is it the only remaining group?
546 | // _util.log(0, "trigger", options.name, "->", "kill");
547 | // was the last member, remove the whole group
548 | removeTriggerGroup();
549 | } else {
550 | Indicator.triggerGroup.members.splice(Indicator.triggerGroup.members.indexOf(Indicator), 1); // just remove from memberlist of old group
551 | _ctrl._indicators.updateTriggerGroupLabel(Indicator.triggerGroup);
552 | _ctrl._indicators.updateTriggerGroupPositions(Indicator.triggerGroup);
553 | // _util.log(0, "trigger", options.name, "->", "removing from previous member list");
554 | }
555 | }
556 | // join new group
557 | group.members.push(Indicator);
558 | Indicator.triggerGroup = group;
559 | _ctrl._indicators.updateTriggerGroupLabel(group);
560 | return;
561 | }
562 | }
563 |
564 | // at this point I am obviously out of sync and don't match any other group
565 | if (Indicator.triggerGroup) {
566 | if (Indicator.triggerGroup.members.length === 1) {
567 | // _util.log(0, "trigger", options.name, "->", "updating existing");
568 | // out of sync but i'm the only member => just change and update
569 | Indicator.triggerGroup.triggerHook = triggerHook;
570 | _ctrl._indicators.updateTriggerGroupPositions(Indicator.triggerGroup);
571 | return;
572 | } else {
573 | // _util.log(0, "trigger", options.name, "->", "removing from previous member list");
574 | Indicator.triggerGroup.members.splice(Indicator.triggerGroup.members.indexOf(Indicator), 1); // just remove from memberlist of old group
575 | _ctrl._indicators.updateTriggerGroupLabel(Indicator.triggerGroup);
576 | _ctrl._indicators.updateTriggerGroupPositions(Indicator.triggerGroup);
577 | Indicator.triggerGroup = undefined; // need a brand new group...
578 | }
579 | }
580 | // _util.log(0, "trigger", options.name, "->", "add a new one");
581 | // did not find any match, make new trigger group
582 | addTriggerGroup();
583 | };
584 | };
585 |
586 | /*
587 | * ----------------------------------------------------------------
588 | * Templates for the indicators
589 | * ----------------------------------------------------------------
590 | */
591 | var TPL = {
592 | start: function (color) {
593 | // inner element (for bottom offset -1, while keeping top position 0)
594 | var inner = document.createElement("div");
595 | inner.textContent = "start";
596 | _util.css(inner, {
597 | position: "absolute",
598 | overflow: "visible",
599 | "border-width": 0,
600 | "border-style": "solid",
601 | color: color,
602 | "border-color": color
603 | });
604 | var e = document.createElement('div');
605 | // wrapper
606 | _util.css(e, {
607 | position: "absolute",
608 | overflow: "visible",
609 | width: 0,
610 | height: 0
611 | });
612 | e.appendChild(inner);
613 | return e;
614 | },
615 | end: function (color) {
616 | var e = document.createElement('div');
617 | e.textContent = "end";
618 | _util.css(e, {
619 | position: "absolute",
620 | overflow: "visible",
621 | "border-width": 0,
622 | "border-style": "solid",
623 | color: color,
624 | "border-color": color
625 | });
626 | return e;
627 | },
628 | bounds: function () {
629 | var e = document.createElement('div');
630 | _util.css(e, {
631 | position: "absolute",
632 | overflow: "visible",
633 | "white-space": "nowrap",
634 | "pointer-events": "none",
635 | "font-size": FONT_SIZE
636 | });
637 | e.style.zIndex = ZINDEX;
638 | return e;
639 | },
640 | trigger: function (color) {
641 | // inner to be above or below line but keep position
642 | var inner = document.createElement('div');
643 | inner.textContent = "trigger";
644 | _util.css(inner, {
645 | position: "relative",
646 | });
647 | // inner wrapper for right: 0 and main element has no size
648 | var w = document.createElement('div');
649 | _util.css(w, {
650 | position: "absolute",
651 | overflow: "visible",
652 | "border-width": 0,
653 | "border-style": "solid",
654 | color: color,
655 | "border-color": color
656 | });
657 | w.appendChild(inner);
658 | // wrapper
659 | var e = document.createElement('div');
660 | _util.css(e, {
661 | position: "fixed",
662 | overflow: "visible",
663 | "white-space": "nowrap",
664 | "pointer-events": "none",
665 | "font-size": FONT_SIZE
666 | });
667 | e.style.zIndex = ZINDEX;
668 | e.appendChild(w);
669 | return e;
670 | },
671 | };
672 |
673 | }
674 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | This project was bootstrapped with [Create React App](https://github.com/facebookincubator/create-react-app).
2 |
3 | Below you will find some information on how to perform common tasks.
4 | You can find the most recent version of this guide [here](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md).
5 |
6 | ## Table of Contents
7 |
8 | - [Updating to New Releases](#updating-to-new-releases)
9 | - [Sending Feedback](#sending-feedback)
10 | - [Folder Structure](#folder-structure)
11 | - [Available Scripts](#available-scripts)
12 | - [npm start](#npm-start)
13 | - [npm test](#npm-test)
14 | - [npm run build](#npm-run-build)
15 | - [npm run eject](#npm-run-eject)
16 | - [Supported Language Features and Polyfills](#supported-language-features-and-polyfills)
17 | - [Syntax Highlighting in the Editor](#syntax-highlighting-in-the-editor)
18 | - [Displaying Lint Output in the Editor](#displaying-lint-output-in-the-editor)
19 | - [Debugging in the Editor](#debugging-in-the-editor)
20 | - [Formatting Code Automatically](#formatting-code-automatically)
21 | - [Changing the Page ``](#changing-the-page-title)
22 | - [Installing a Dependency](#installing-a-dependency)
23 | - [Importing a Component](#importing-a-component)
24 | - [Code Splitting](#code-splitting)
25 | - [Adding a Stylesheet](#adding-a-stylesheet)
26 | - [Post-Processing CSS](#post-processing-css)
27 | - [Adding a CSS Preprocessor (Sass, Less etc.)](#adding-a-css-preprocessor-sass-less-etc)
28 | - [Adding Images, Fonts, and Files](#adding-images-fonts-and-files)
29 | - [Using the `public` Folder](#using-the-public-folder)
30 | - [Changing the HTML](#changing-the-html)
31 | - [Adding Assets Outside of the Module System](#adding-assets-outside-of-the-module-system)
32 | - [When to Use the `public` Folder](#when-to-use-the-public-folder)
33 | - [Using Global Variables](#using-global-variables)
34 | - [Adding Bootstrap](#adding-bootstrap)
35 | - [Using a Custom Theme](#using-a-custom-theme)
36 | - [Adding Flow](#adding-flow)
37 | - [Adding Custom Environment Variables](#adding-custom-environment-variables)
38 | - [Referencing Environment Variables in the HTML](#referencing-environment-variables-in-the-html)
39 | - [Adding Temporary Environment Variables In Your Shell](#adding-temporary-environment-variables-in-your-shell)
40 | - [Adding Development Environment Variables In `.env`](#adding-development-environment-variables-in-env)
41 | - [Can I Use Decorators?](#can-i-use-decorators)
42 | - [Integrating with an API Backend](#integrating-with-an-api-backend)
43 | - [Node](#node)
44 | - [Ruby on Rails](#ruby-on-rails)
45 | - [Proxying API Requests in Development](#proxying-api-requests-in-development)
46 | - ["Invalid Host Header" Errors After Configuring Proxy](#invalid-host-header-errors-after-configuring-proxy)
47 | - [Configuring the Proxy Manually](#configuring-the-proxy-manually)
48 | - [Configuring a WebSocket Proxy](#configuring-a-websocket-proxy)
49 | - [Using HTTPS in Development](#using-https-in-development)
50 | - [Generating Dynamic `` Tags on the Server](#generating-dynamic-meta-tags-on-the-server)
51 | - [Pre-Rendering into Static HTML Files](#pre-rendering-into-static-html-files)
52 | - [Injecting Data from the Server into the Page](#injecting-data-from-the-server-into-the-page)
53 | - [Running Tests](#running-tests)
54 | - [Filename Conventions](#filename-conventions)
55 | - [Command Line Interface](#command-line-interface)
56 | - [Version Control Integration](#version-control-integration)
57 | - [Writing Tests](#writing-tests)
58 | - [Testing Components](#testing-components)
59 | - [Using Third Party Assertion Libraries](#using-third-party-assertion-libraries)
60 | - [Initializing Test Environment](#initializing-test-environment)
61 | - [Focusing and Excluding Tests](#focusing-and-excluding-tests)
62 | - [Coverage Reporting](#coverage-reporting)
63 | - [Continuous Integration](#continuous-integration)
64 | - [Disabling jsdom](#disabling-jsdom)
65 | - [Snapshot Testing](#snapshot-testing)
66 | - [Editor Integration](#editor-integration)
67 | - [Developing Components in Isolation](#developing-components-in-isolation)
68 | - [Getting Started with Storybook](#getting-started-with-storybook)
69 | - [Getting Started with Styleguidist](#getting-started-with-styleguidist)
70 | - [Making a Progressive Web App](#making-a-progressive-web-app)
71 | - [Opting Out of Caching](#opting-out-of-caching)
72 | - [Offline-First Considerations](#offline-first-considerations)
73 | - [Progressive Web App Metadata](#progressive-web-app-metadata)
74 | - [Analyzing the Bundle Size](#analyzing-the-bundle-size)
75 | - [Deployment](#deployment)
76 | - [Static Server](#static-server)
77 | - [Other Solutions](#other-solutions)
78 | - [Serving Apps with Client-Side Routing](#serving-apps-with-client-side-routing)
79 | - [Building for Relative Paths](#building-for-relative-paths)
80 | - [Azure](#azure)
81 | - [Firebase](#firebase)
82 | - [GitHub Pages](#github-pages)
83 | - [Heroku](#heroku)
84 | - [Netlify](#netlify)
85 | - [Now](#now)
86 | - [S3 and CloudFront](#s3-and-cloudfront)
87 | - [Surge](#surge)
88 | - [Advanced Configuration](#advanced-configuration)
89 | - [Troubleshooting](#troubleshooting)
90 | - [`npm start` doesn’t detect changes](#npm-start-doesnt-detect-changes)
91 | - [`npm test` hangs on macOS Sierra](#npm-test-hangs-on-macos-sierra)
92 | - [`npm run build` exits too early](#npm-run-build-exits-too-early)
93 | - [`npm run build` fails on Heroku](#npm-run-build-fails-on-heroku)
94 | - [`npm run build` fails to minify](#npm-run-build-fails-to-minify)
95 | - [Moment.js locales are missing](#momentjs-locales-are-missing)
96 | - [Something Missing?](#something-missing)
97 |
98 | ## Updating to New Releases
99 |
100 | Create React App is divided into two packages:
101 |
102 | * `create-react-app` is a global command-line utility that you use to create new projects.
103 | * `react-scripts` is a development dependency in the generated projects (including this one).
104 |
105 | You almost never need to update `create-react-app` itself: it delegates all the setup to `react-scripts`.
106 |
107 | When you run `create-react-app`, it always creates the project with the latest version of `react-scripts` so you’ll get all the new features and improvements in newly created apps automatically.
108 |
109 | To update an existing project to a new version of `react-scripts`, [open the changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md), find the version you’re currently on (check `package.json` in this folder if you’re not sure), and apply the migration instructions for the newer versions.
110 |
111 | In most cases bumping the `react-scripts` version in `package.json` and running `npm install` in this folder should be enough, but it’s good to consult the [changelog](https://github.com/facebookincubator/create-react-app/blob/master/CHANGELOG.md) for potential breaking changes.
112 |
113 | We commit to keeping the breaking changes minimal so you can upgrade `react-scripts` painlessly.
114 |
115 | ## Sending Feedback
116 |
117 | We are always open to [your feedback](https://github.com/facebookincubator/create-react-app/issues).
118 |
119 | ## Folder Structure
120 |
121 | After creation, your project should look like this:
122 |
123 | ```
124 | my-app/
125 | README.md
126 | node_modules/
127 | package.json
128 | public/
129 | index.html
130 | favicon.ico
131 | src/
132 | App.css
133 | App.js
134 | App.test.js
135 | index.css
136 | index.js
137 | logo.svg
138 | ```
139 |
140 | For the project to build, **these files must exist with exact filenames**:
141 |
142 | * `public/index.html` is the page template;
143 | * `src/index.js` is the JavaScript entry point.
144 |
145 | You can delete or rename the other files.
146 |
147 | You may create subdirectories inside `src`. For faster rebuilds, only files inside `src` are processed by Webpack.
148 | You need to **put any JS and CSS files inside `src`**, otherwise Webpack won’t see them.
149 |
150 | Only files inside `public` can be used from `public/index.html`.
151 | Read instructions below for using assets from JavaScript and HTML.
152 |
153 | You can, however, create more top-level directories.
154 | They will not be included in the production build so you can use them for things like documentation.
155 |
156 | ## Available Scripts
157 |
158 | In the project directory, you can run:
159 |
160 | ### `npm start`
161 |
162 | Runs the app in the development mode.
163 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
164 |
165 | The page will reload if you make edits.
166 | You will also see any lint errors in the console.
167 |
168 | ### `npm test`
169 |
170 | Launches the test runner in the interactive watch mode.
171 | See the section about [running tests](#running-tests) for more information.
172 |
173 | ### `npm run build`
174 |
175 | Builds the app for production to the `build` folder.
176 | It correctly bundles React in production mode and optimizes the build for the best performance.
177 |
178 | The build is minified and the filenames include the hashes.
179 | Your app is ready to be deployed!
180 |
181 | See the section about [deployment](#deployment) for more information.
182 |
183 | ### `npm run eject`
184 |
185 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!**
186 |
187 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
188 |
189 | Instead, it will copy all the configuration files and the transitive dependencies (Webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
190 |
191 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
192 |
193 | ## Supported Language Features and Polyfills
194 |
195 | This project supports a superset of the latest JavaScript standard.
196 | In addition to [ES6](https://github.com/lukehoban/es6features) syntax features, it also supports:
197 |
198 | * [Exponentiation Operator](https://github.com/rwaldron/exponentiation-operator) (ES2016).
199 | * [Async/await](https://github.com/tc39/ecmascript-asyncawait) (ES2017).
200 | * [Object Rest/Spread Properties](https://github.com/sebmarkbage/ecmascript-rest-spread) (stage 3 proposal).
201 | * [Dynamic import()](https://github.com/tc39/proposal-dynamic-import) (stage 3 proposal)
202 | * [Class Fields and Static Properties](https://github.com/tc39/proposal-class-public-fields) (stage 2 proposal).
203 | * [JSX](https://facebook.github.io/react/docs/introducing-jsx.html) and [Flow](https://flowtype.org/) syntax.
204 |
205 | Learn more about [different proposal stages](https://babeljs.io/docs/plugins/#presets-stage-x-experimental-presets-).
206 |
207 | While we recommend to use experimental proposals with some caution, Facebook heavily uses these features in the product code, so we intend to provide [codemods](https://medium.com/@cpojer/effective-javascript-codemods-5a6686bb46fb) if any of these proposals change in the future.
208 |
209 | Note that **the project only includes a few ES6 [polyfills](https://en.wikipedia.org/wiki/Polyfill)**:
210 |
211 | * [`Object.assign()`](https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign) via [`object-assign`](https://github.com/sindresorhus/object-assign).
212 | * [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) via [`promise`](https://github.com/then/promise).
213 | * [`fetch()`](https://developer.mozilla.org/en/docs/Web/API/Fetch_API) via [`whatwg-fetch`](https://github.com/github/fetch).
214 |
215 | If you use any other ES6+ features that need **runtime support** (such as `Array.from()` or `Symbol`), make sure you are including the appropriate polyfills manually, or that the browsers you are targeting already support them.
216 |
217 | ## Syntax Highlighting in the Editor
218 |
219 | To configure the syntax highlighting in your favorite text editor, head to the [relevant Babel documentation page](https://babeljs.io/docs/editors) and follow the instructions. Some of the most popular editors are covered.
220 |
221 | ## Displaying Lint Output in the Editor
222 |
223 | >Note: this feature is available with `react-scripts@0.2.0` and higher.
224 | >It also only works with npm 3 or higher.
225 |
226 | Some editors, including Sublime Text, Atom, and Visual Studio Code, provide plugins for ESLint.
227 |
228 | They are not required for linting. You should see the linter output right in your terminal as well as the browser console. However, if you prefer the lint results to appear right in your editor, there are some extra steps you can do.
229 |
230 | You would need to install an ESLint plugin for your editor first. Then, add a file called `.eslintrc` to the project root:
231 |
232 | ```js
233 | {
234 | "extends": "react-app"
235 | }
236 | ```
237 |
238 | Now your editor should report the linting warnings.
239 |
240 | Note that even if you edit your `.eslintrc` file further, these changes will **only affect the editor integration**. They won’t affect the terminal and in-browser lint output. This is because Create React App intentionally provides a minimal set of rules that find common mistakes.
241 |
242 | If you want to enforce a coding style for your project, consider using [Prettier](https://github.com/jlongster/prettier) instead of ESLint style rules.
243 |
244 | ## Debugging in the Editor
245 |
246 | **This feature is currently only supported by [Visual Studio Code](https://code.visualstudio.com) and [WebStorm](https://www.jetbrains.com/webstorm/).**
247 |
248 | Visual Studio Code and WebStorm support debugging out of the box with Create React App. This enables you as a developer to write and debug your React code without leaving the editor, and most importantly it enables you to have a continuous development workflow, where context switching is minimal, as you don’t have to switch between tools.
249 |
250 | ### Visual Studio Code
251 |
252 | You would need to have the latest version of [VS Code](https://code.visualstudio.com) and VS Code [Chrome Debugger Extension](https://marketplace.visualstudio.com/items?itemName=msjsdiag.debugger-for-chrome) installed.
253 |
254 | Then add the block below to your `launch.json` file and put it inside the `.vscode` folder in your app’s root directory.
255 |
256 | ```json
257 | {
258 | "version": "0.2.0",
259 | "configurations": [{
260 | "name": "Chrome",
261 | "type": "chrome",
262 | "request": "launch",
263 | "url": "http://localhost:3000",
264 | "webRoot": "${workspaceRoot}/src",
265 | "userDataDir": "${workspaceRoot}/.vscode/chrome",
266 | "sourceMapPathOverrides": {
267 | "webpack:///src/*": "${webRoot}/*"
268 | }
269 | }]
270 | }
271 | ```
272 | >Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration).
273 |
274 | Start your app by running `npm start`, and start debugging in VS Code by pressing `F5` or by clicking the green debug icon. You can now write code, set breakpoints, make changes to the code, and debug your newly modified code—all from your editor.
275 |
276 | ### WebStorm
277 |
278 | You would need to have [WebStorm](https://www.jetbrains.com/webstorm/) and [JetBrains IDE Support](https://chrome.google.com/webstore/detail/jetbrains-ide-support/hmhgeddbohgjknpmjagkdomcpobmllji) Chrome extension installed.
279 |
280 | In the WebStorm menu `Run` select `Edit Configurations...`. Then click `+` and select `JavaScript Debug`. Paste `http://localhost:3000` into the URL field and save the configuration.
281 |
282 | >Note: the URL may be different if you've made adjustments via the [HOST or PORT environment variables](#advanced-configuration).
283 |
284 | Start your app by running `npm start`, then press `^D` on macOS or `F9` on Windows and Linux or click the green debug icon to start debugging in WebStorm.
285 |
286 | The same way you can debug your application in IntelliJ IDEA Ultimate, PhpStorm, PyCharm Pro, and RubyMine.
287 |
288 | ## Formatting Code Automatically
289 |
290 | Prettier is an opinionated code formatter with support for JavaScript, CSS and JSON. With Prettier you can format the code you write automatically to ensure a code style within your project. See the [Prettier's GitHub page](https://github.com/prettier/prettier) for more information, and look at this [page to see it in action](https://prettier.github.io/prettier/).
291 |
292 | To format our code whenever we make a commit in git, we need to install the following dependencies:
293 |
294 | ```sh
295 | npm install --save husky lint-staged prettier
296 | ```
297 |
298 | Alternatively you may use `yarn`:
299 |
300 | ```sh
301 | yarn add husky lint-staged prettier
302 | ```
303 |
304 | * `husky` makes it easy to use githooks as if they are npm scripts.
305 | * `lint-staged` allows us to run scripts on staged files in git. See this [blog post about lint-staged to learn more about it](https://medium.com/@okonetchnikov/make-linting-great-again-f3890e1ad6b8).
306 | * `prettier` is the JavaScript formatter we will run before commits.
307 |
308 | Now we can make sure every file is formatted correctly by adding a few lines to the `package.json` in the project root.
309 |
310 | Add the following line to `scripts` section:
311 |
312 | ```diff
313 | "scripts": {
314 | + "precommit": "lint-staged",
315 | "start": "react-scripts start",
316 | "build": "react-scripts build",
317 | ```
318 |
319 | Next we add a 'lint-staged' field to the `package.json`, for example:
320 |
321 | ```diff
322 | "dependencies": {
323 | // ...
324 | },
325 | + "lint-staged": {
326 | + "src/**/*.{js,jsx,json,css}": [
327 | + "prettier --single-quote --write",
328 | + "git add"
329 | + ]
330 | + },
331 | "scripts": {
332 | ```
333 |
334 | Now, whenever you make a commit, Prettier will format the changed files automatically. You can also run `./node_modules/.bin/prettier --single-quote --write "src/**/*.{js,jsx}"` to format your entire project for the first time.
335 |
336 | Next you might want to integrate Prettier in your favorite editor. Read the section on [Editor Integration](https://github.com/prettier/prettier#editor-integration) on the Prettier GitHub page.
337 |
338 | ## Changing the Page ``
339 |
340 | You can find the source HTML file in the `public` folder of the generated project. You may edit the `` tag in it to change the title from “React App” to anything else.
341 |
342 | Note that normally you wouldn’t edit files in the `public` folder very often. For example, [adding a stylesheet](#adding-a-stylesheet) is done without touching the HTML.
343 |
344 | If you need to dynamically update the page title based on the content, you can use the browser [`document.title`](https://developer.mozilla.org/en-US/docs/Web/API/Document/title) API. For more complex scenarios when you want to change the title from React components, you can use [React Helmet](https://github.com/nfl/react-helmet), a third party library.
345 |
346 | If you use a custom server for your app in production and want to modify the title before it gets sent to the browser, you can follow advice in [this section](#generating-dynamic-meta-tags-on-the-server). Alternatively, you can pre-build each page as a static HTML file which then loads the JavaScript bundle, which is covered [here](#pre-rendering-into-static-html-files).
347 |
348 | ## Installing a Dependency
349 |
350 | The generated project includes React and ReactDOM as dependencies. It also includes a set of scripts used by Create React App as a development dependency. You may install other dependencies (for example, React Router) with `npm`:
351 |
352 | ```sh
353 | npm install --save react-router
354 | ```
355 |
356 | Alternatively you may use `yarn`:
357 |
358 | ```sh
359 | yarn add react-router
360 | ```
361 |
362 | This works for any library, not just `react-router`.
363 |
364 | ## Importing a Component
365 |
366 | This project setup supports ES6 modules thanks to Babel.
367 | While you can still use `require()` and `module.exports`, we encourage you to use [`import` and `export`](http://exploringjs.com/es6/ch_modules.html) instead.
368 |
369 | For example:
370 |
371 | ### `Button.js`
372 |
373 | ```js
374 | import React, { Component } from 'react';
375 |
376 | class Button extends Component {
377 | render() {
378 | // ...
379 | }
380 | }
381 |
382 | export default Button; // Don’t forget to use export default!
383 | ```
384 |
385 | ### `DangerButton.js`
386 |
387 |
388 | ```js
389 | import React, { Component } from 'react';
390 | import Button from './Button'; // Import a component from another file
391 |
392 | class DangerButton extends Component {
393 | render() {
394 | return ;
395 | }
396 | }
397 |
398 | export default DangerButton;
399 | ```
400 |
401 | Be aware of the [difference between default and named exports](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281). It is a common source of mistakes.
402 |
403 | We suggest that you stick to using default imports and exports when a module only exports a single thing (for example, a component). That’s what you get when you use `export default Button` and `import Button from './Button'`.
404 |
405 | Named exports are useful for utility modules that export several functions. A module may have at most one default export and as many named exports as you like.
406 |
407 | Learn more about ES6 modules:
408 |
409 | * [When to use the curly braces?](http://stackoverflow.com/questions/36795819/react-native-es-6-when-should-i-use-curly-braces-for-import/36796281#36796281)
410 | * [Exploring ES6: Modules](http://exploringjs.com/es6/ch_modules.html)
411 | * [Understanding ES6: Modules](https://leanpub.com/understandinges6/read#leanpub-auto-encapsulating-code-with-modules)
412 |
413 | ## Code Splitting
414 |
415 | Instead of downloading the entire app before users can use it, code splitting allows you to split your code into small chunks which you can then load on demand.
416 |
417 | This project setup supports code splitting via [dynamic `import()`](http://2ality.com/2017/01/import-operator.html#loading-code-on-demand). Its [proposal](https://github.com/tc39/proposal-dynamic-import) is in stage 3. The `import()` function-like form takes the module name as an argument and returns a [`Promise`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) which always resolves to the namespace object of the module.
418 |
419 | Here is an example:
420 |
421 | ### `moduleA.js`
422 |
423 | ```js
424 | const moduleA = 'Hello';
425 |
426 | export { moduleA };
427 | ```
428 | ### `App.js`
429 |
430 | ```js
431 | import React, { Component } from 'react';
432 |
433 | class App extends Component {
434 | handleClick = () => {
435 | import('./moduleA')
436 | .then(({ moduleA }) => {
437 | // Use moduleA
438 | })
439 | .catch(err => {
440 | // Handle failure
441 | });
442 | };
443 |
444 | render() {
445 | return (
446 |
447 |
448 |
449 | );
450 | }
451 | }
452 |
453 | export default App;
454 | ```
455 |
456 | This will make `moduleA.js` and all its unique dependencies as a separate chunk that only loads after the user clicks the 'Load' button.
457 |
458 | You can also use it with `async` / `await` syntax if you prefer it.
459 |
460 | ### With React Router
461 |
462 | If you are using React Router check out [this tutorial](http://serverless-stack.com/chapters/code-splitting-in-create-react-app.html) on how to use code splitting with it. You can find the companion GitHub repository [here](https://github.com/AnomalyInnovations/serverless-stack-demo-client/tree/code-splitting-in-create-react-app).
463 |
464 | ## Adding a Stylesheet
465 |
466 | This project setup uses [Webpack](https://webpack.js.org/) for handling all assets. Webpack offers a custom way of “extending” the concept of `import` beyond JavaScript. To express that a JavaScript file depends on a CSS file, you need to **import the CSS from the JavaScript file**:
467 |
468 | ### `Button.css`
469 |
470 | ```css
471 | .Button {
472 | padding: 20px;
473 | }
474 | ```
475 |
476 | ### `Button.js`
477 |
478 | ```js
479 | import React, { Component } from 'react';
480 | import './Button.css'; // Tell Webpack that Button.js uses these styles
481 |
482 | class Button extends Component {
483 | render() {
484 | // You can use them as regular CSS styles
485 | return ;
486 | }
487 | }
488 | ```
489 |
490 | **This is not required for React** but many people find this feature convenient. You can read about the benefits of this approach [here](https://medium.com/seek-ui-engineering/block-element-modifying-your-javascript-components-d7f99fcab52b). However you should be aware that this makes your code less portable to other build tools and environments than Webpack.
491 |
492 | In development, expressing dependencies this way allows your styles to be reloaded on the fly as you edit them. In production, all CSS files will be concatenated into a single minified `.css` file in the build output.
493 |
494 | If you are concerned about using Webpack-specific semantics, you can put all your CSS right into `src/index.css`. It would still be imported from `src/index.js`, but you could always remove that import if you later migrate to a different build tool.
495 |
496 | ## Post-Processing CSS
497 |
498 | This project setup minifies your CSS and adds vendor prefixes to it automatically through [Autoprefixer](https://github.com/postcss/autoprefixer) so you don’t need to worry about it.
499 |
500 | For example, this:
501 |
502 | ```css
503 | .App {
504 | display: flex;
505 | flex-direction: row;
506 | align-items: center;
507 | }
508 | ```
509 |
510 | becomes this:
511 |
512 | ```css
513 | .App {
514 | display: -webkit-box;
515 | display: -ms-flexbox;
516 | display: flex;
517 | -webkit-box-orient: horizontal;
518 | -webkit-box-direction: normal;
519 | -ms-flex-direction: row;
520 | flex-direction: row;
521 | -webkit-box-align: center;
522 | -ms-flex-align: center;
523 | align-items: center;
524 | }
525 | ```
526 |
527 | If you need to disable autoprefixing for some reason, [follow this section](https://github.com/postcss/autoprefixer#disabling).
528 |
529 | ## Adding a CSS Preprocessor (Sass, Less etc.)
530 |
531 | Generally, we recommend that you don’t reuse the same CSS classes across different components. For example, instead of using a `.Button` CSS class in `` and `` components, we recommend creating a `