├── .github
└── workflows
│ ├── playwright.yml
│ └── pushToTestAction.yml
├── .gitignore
├── .npmignore
├── README.md
├── demo
└── scroll_3.gif
├── package-lock.json
├── package.json
├── playwright.config.ts
├── public
└── index.html
├── rollup.config.dev.js
├── rollup.config.js
├── src
└── index.tsx
├── test
├── TimelineAnimation
│ ├── TimelineAnimation.jsx
│ ├── index.js
│ └── styles.css
└── index.js
├── tests
└── timeline.spec.ts
└── tsconfig.json
/.github/workflows/playwright.yml:
--------------------------------------------------------------------------------
1 | name: Playwright Tests
2 | on:
3 | push:
4 | branches: [ main ]
5 | jobs:
6 | test:
7 | timeout-minutes: 60
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - uses: actions/setup-node@v3
12 | with:
13 | node-version: 16
14 | - name: Install dependencies
15 | run: npm ci
16 | - name: Install Playwright Browsers
17 | run: npx playwright install --with-deps
18 | - name: Run Playwright tests
19 | run: npx playwright test
20 | - uses: actions/upload-artifact@v3
21 | if: always()
22 | with:
23 | name: playwright-report
24 | path: playwright-report/
25 | retention-days: 30
26 |
--------------------------------------------------------------------------------
/.github/workflows/pushToTestAction.yml:
--------------------------------------------------------------------------------
1 | name: push to test
2 | on:
3 | push:
4 | branches: [master, main]
5 | jobs:
6 | merge-master-to-test:
7 | timeout-minutes: 5
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/checkout@v3
11 | - name: Set Git config
12 | run: |
13 | git config --local user.email "kashuba-aleksandr@mail.ru"
14 | git config --local user.name "akashuba"
15 | - name: Merge master back to test
16 | run: |
17 | git fetch --unshallow
18 | git checkout test
19 | git pull
20 | git merge --no-ff main -m "Auto-merge master to test"
21 | git push
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | lerna-debug.log*
8 |
9 | # Directory for instrumented libs generated by jscoverage/JSCover
10 | lib-cov
11 |
12 | # Coverage directory used by tools like istanbul
13 | coverage
14 | *.lcov
15 |
16 | # nyc test coverage
17 | .nyc_output
18 |
19 | # node-waf configuration
20 | .lock-wscript
21 |
22 | # Compiled binary addons (https://nodejs.org/api/addons.html)
23 | build/Release
24 |
25 | # Dependency directories
26 | node_modules/
27 | jspm_packages/
28 |
29 | # TypeScript v1 declaration files
30 | typings/
31 |
32 | # TypeScript cache
33 | *.tsbuildinfo
34 |
35 | # Optional npm cache directory
36 | .npm
37 |
38 | # Optional eslint cache
39 | .eslintcache
40 |
41 | # Output of 'npm pack'
42 | *.tgz
43 |
44 | # Yarn Integrity file
45 | .yarn-integrity
46 |
47 | # generate output
48 | dist
49 |
50 | # vscode
51 | .vscode
52 | /test-results/
53 | /playwright-report/
54 | /playwright/.cache/
55 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akashuba/react-timeline-animation/f279375e51230909267c650ad3b375abbfa3dd1f/.npmignore
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React scroll animation component.
2 | 
3 |
4 | #### Could be used for timeline filling or any animations related to scrolling and crossing the middle of the screen. Just wrap the animated component with TimelineObserver.
5 |
6 |
7 |

8 |
9 |
10 | ## Demo [codesandbox](https://codesandbox.io/s/brave-kepler-fdbzv?file=/src/App.js:0-1097) 🚀
11 |
12 | ## How to use it
13 |
14 | ### 1. Installation
15 |
16 | ```bash
17 | npm install --save react-timeline-animation
18 | ```
19 |
20 | or
21 |
22 | ```bash
23 | yarn add react-timeline-animation
24 | ```
25 | ### 2. Quick start
26 | Important to add a unique id to the observed element (id="timeline100500").
27 | ``` javascript
28 | ;
29 | ```
30 |
31 | Component using react "render prop" pattern.
32 |
33 | ```javascript
34 | (
38 |
43 | )}
44 | />;
45 | ```
46 |
47 | ```javascript
48 | const Timeline = ({ setObserver, callback }) => {
49 | const timeline = useRef(null);
50 |
51 | // It Will be fired when the element crossed the middle of the screen.
52 | const someCallback = () => {
53 | callback();
54 | };
55 |
56 | useEffect(() => {
57 | if (timeline.current) {
58 | setObserver(timeline.current, someCallback);
59 | }
60 | }, []);
61 |
62 | return ;
63 | };
64 | ```
65 |
66 | ## Options (props) 🛠
67 |
68 | #### `initialColor`: not required. Initial color of observable element.
69 |
70 | #### `fillColor`: not required. Color to fill element.
71 |
72 | #### `handleObserve`: required. "render prop" to handle observable element.
73 | #### `hasReverse`: not required. Allow to scroll in both directions.
74 |
75 | ```typescript
76 | interface TimelineObserverProps {
77 | handleObserve?: (
78 | observer: (target: Element, callbackFn?: () => void) => void
79 | ) => JSX.Element;
80 | initialColor?: string;
81 | fillColor?: string;
82 | hasReverse?: boolean;
83 | }
84 | ```
--------------------------------------------------------------------------------
/demo/scroll_3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/akashuba/react-timeline-animation/f279375e51230909267c650ad3b375abbfa3dd1f/demo/scroll_3.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-timeline-animation",
3 | "version": "1.2.3",
4 | "description": "Scroll animation component",
5 | "main": "dist/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/akashuba/react-timeline-animation"
9 | },
10 | "scripts": {
11 | "build": "rollup -c rollup.config.js",
12 | "start": "rollup -c rollup.config.js -w",
13 | "build:dev": "MODE=build rollup -c rollup.config.dev.js",
14 | "start:dev": "MODE=start rollup -c rollup.config.dev.js -w",
15 | "test": "npx playwright test"
16 | },
17 | "author": "akashuba",
18 | "license": "ISC",
19 | "devDependencies": {
20 | "@babel/core": "^7.20.2",
21 | "@babel/plugin-external-helpers": "^7.0.0",
22 | "@babel/plugin-proposal-optional-chaining": "^7.18.9",
23 | "@babel/preset-env": "^7.0.0",
24 | "@babel/preset-react": "^7.18.6",
25 | "@babel/preset-typescript": "^7.18.6",
26 | "@babel/runtime-corejs2": "^7.0.0",
27 | "@playwright/test": "^1.28.1",
28 | "@rollup/plugin-babel": "^6.0.3",
29 | "@rollup/plugin-commonjs": "^23.0.2",
30 | "@rollup/plugin-html": "^1.0.1",
31 | "@rollup/plugin-node-resolve": "^15.0.1",
32 | "@rollup/plugin-replace": "^5.0.1",
33 | "@rollup/plugin-typescript": "^9.0.2",
34 | "@types/react": "^17.0.19",
35 | "@types/react-dom": "^17.0.9",
36 | "babel-plugin-external-helpers": "^6.22.0",
37 | "postcss": "^8.4.19",
38 | "react": "^16.8.0",
39 | "react-dom": "^16.8.0",
40 | "rollup": "^2.56.3",
41 | "rollup-plugin-commonjs": "^9.1.3",
42 | "rollup-plugin-json": "^3.0.0",
43 | "rollup-plugin-livereload": "^2.0.5",
44 | "rollup-plugin-postcss": "^4.0.2",
45 | "rollup-plugin-progress": "^1.1.2",
46 | "rollup-plugin-sass": "^1.2.7",
47 | "rollup-plugin-serve": "^2.0.1",
48 | "rollup-plugin-typescript2": "^0.30.0",
49 | "typescript": "^4.4.2"
50 | },
51 | "peerDependencies": {
52 | "react": ">= 16.8.0",
53 | "react-dom": ">= 16.8.0"
54 | },
55 | "files": [
56 | "dist"
57 | ],
58 | "keywords": [
59 | "react",
60 | "typescript",
61 | "animation",
62 | "timeline"
63 | ]
64 | }
65 |
--------------------------------------------------------------------------------
/playwright.config.ts:
--------------------------------------------------------------------------------
1 | // @ts-check
2 | const { devices } = require('@playwright/test');
3 |
4 | /**
5 | * Read environment variables from file.
6 | * https://github.com/motdotla/dotenv
7 | */
8 | // require('dotenv').config();
9 |
10 |
11 | /**
12 | * @see https://playwright.dev/docs/test-configuration
13 | * @type {import('@playwright/test').PlaywrightTestConfig}
14 | */
15 | const config = {
16 | testDir: './tests',
17 | /* Maximum time one test can run for. */
18 | timeout: 30 * 1000,
19 | expect: {
20 | /**
21 | * Maximum time expect() should wait for the condition to be met.
22 | * For example in `await expect(locator).toHaveText();`
23 | */
24 | timeout: 5000
25 | },
26 | /* Run tests in files in parallel */
27 | fullyParallel: true,
28 | /* Fail the build on CI if you accidentally left test.only in the source code. */
29 | forbidOnly: !!process.env.CI,
30 | /* Retry on CI only */
31 | retries: process.env.CI ? 2 : 0,
32 | /* Opt out of parallel tests on CI. */
33 | workers: process.env.CI ? 1 : undefined,
34 | /* Reporter to use. See https://playwright.dev/docs/test-reporters */
35 | reporter: 'html',
36 | /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
37 | webServer: {
38 | command: 'npm run start:dev',
39 | // url: 'http://localhost:3000/',
40 | port: 3000,
41 | timeout: 50000,
42 | reuseExistingServer: true,
43 | },
44 | use: {
45 | /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */
46 | actionTimeout: 0,
47 | /* Base URL to use in actions like `await page.goto('/')`. */
48 | // baseURL: 'http://localhost:3000',
49 |
50 | /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
51 | trace: 'on-first-retry',
52 | },
53 |
54 | /* Configure projects for major browsers */
55 | projects: [
56 | {
57 | name: 'chromium',
58 | use: {
59 | ...devices['Desktop Chrome'],
60 | },
61 | },
62 |
63 | // {
64 | // name: 'firefox',
65 | // use: {
66 | // ...devices['Desktop Firefox'],
67 | // },
68 | // },
69 |
70 | // {
71 | // name: 'webkit',
72 | // use: {
73 | // ...devices['Desktop Safari'],
74 | // },
75 | // },
76 |
77 | /* Test against mobile viewports. */
78 | // {
79 | // name: 'Mobile Chrome',
80 | // use: {
81 | // ...devices['Pixel 5'],
82 | // },
83 | // },
84 | // {
85 | // name: 'Mobile Safari',
86 | // use: {
87 | // ...devices['iPhone 12'],
88 | // },
89 | // },
90 |
91 | /* Test against branded browsers. */
92 | // {
93 | // name: 'Microsoft Edge',
94 | // use: {
95 | // channel: 'msedge',
96 | // },
97 | // },
98 | // {
99 | // name: 'Google Chrome',
100 | // use: {
101 | // channel: 'chrome',
102 | // },
103 | // },
104 | ],
105 |
106 | /* Folder for test artifacts such as screenshots, videos, traces, etc. */
107 | // outputDir: 'test-results/',
108 | };
109 |
110 | module.exports = config;
111 |
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | React - Rollup Test
7 |
8 |
9 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/rollup.config.dev.js:
--------------------------------------------------------------------------------
1 | // import babel from 'rollup-plugin-babel';
2 | import { babel } from '@rollup/plugin-babel';
3 | // import filesize from 'rollup-plugin-filesize';
4 | import { nodeResolve } from '@rollup/plugin-node-resolve';
5 | import progress from 'rollup-plugin-progress';
6 | // import visualizer from 'rollup-plugin-visualizer';
7 | import commonjs from '@rollup/plugin-commonjs';
8 | import json from 'rollup-plugin-json';
9 | import serve from "rollup-plugin-serve";
10 | import livereload from "rollup-plugin-livereload";
11 | import typescript from '@rollup/plugin-typescript';
12 | import replace from '@rollup/plugin-replace';
13 | import postcss from 'rollup-plugin-postcss'
14 |
15 | const isStartMode = process.env.MODE === 'start'
16 |
17 | export default {
18 | input: 'test/index.js',
19 | output: [
20 | {
21 | file: 'dist/index.js',
22 | format: 'iife',
23 | sourcemap: 'inline',
24 | },
25 | ],
26 | plugins: [
27 | typescript(),
28 | progress(),
29 | nodeResolve({
30 | browser: true,
31 | extensions: ['.js', '.jsx', '.tsx']
32 | }),
33 | json(),
34 | commonjs({
35 | include: [
36 | 'node_modules/**',
37 | ],
38 | exclude: [
39 | 'node_modules/process-es6/**',
40 | ],
41 | namedExports: {
42 | 'node_modules/react/index.js': ['Children', 'Component', 'PropTypes', 'createElement'],
43 | 'node_modules/react-dom/index.js': ['render'],
44 | },
45 | }),
46 | babel(
47 | {
48 | presets: [['@babel/preset-env', { modules: false }], '@babel/preset-react', '@babel/preset-typescript'],
49 | // plugins: ['external-helpers'],
50 | }),
51 | // visualizer(),
52 | // filesize(),
53 | (isStartMode ? serve({
54 | verbose: true,
55 | contentBase: ["", "public"],
56 | host: "localhost",
57 | port: 3000,
58 | }) : null),
59 |
60 | replace({
61 | 'process.env.NODE_ENV': JSON.stringify('production')
62 | }),
63 | (isStartMode ? livereload({ watch: "dist" }) : null),
64 | postcss()
65 | ],
66 | };
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import sass from 'rollup-plugin-sass'
2 | import typescript from 'rollup-plugin-typescript2'
3 |
4 | import pkg from './package.json'
5 |
6 | export default {
7 | input: 'src/index.tsx',
8 | output: [
9 | {
10 | file: pkg.main,
11 | format: 'cjs',
12 | exports: 'named',
13 | sourcemap: true,
14 | strict: false
15 | }
16 | ],
17 | plugins: [
18 | sass({ insert: true }),
19 | typescript({ objectHashIgnoreUnknownHack: true })
20 | ],
21 | external: ['react', 'react-dom']
22 | }
--------------------------------------------------------------------------------
/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from "react";
2 |
3 | interface TimelineObserverProps {
4 | handleObserve?: (
5 | observer: (target: Element, callbackFn?: () => void) => void
6 | ) => JSX.Element;
7 |
8 | initialColor?: string;
9 | fillColor?: string;
10 | hasReverse?: boolean;
11 | }
12 |
13 | let halfScreenHeight;
14 | if (typeof window !== "undefined") {
15 | halfScreenHeight = window?.innerHeight / 2;
16 | }
17 |
18 | const options = {
19 | root: null,
20 | rootMargin: "0px",
21 | threshold: 0.2,
22 | };
23 |
24 | interface ObservablesProps {
25 | observable: IntersectionObserverEntry;
26 | isPassed: boolean;
27 | callbackFn?: () => void;
28 | }
29 |
30 | function setObservable({ obs, observableList, callbacks }) {
31 | const obsId = obs?.target?.id;
32 |
33 | if (!observableList.has(obsId)) {
34 | observableList.set(obsId, {
35 | observable: obs,
36 | isPassed: false,
37 | callbackFn: callbacks[obsId] || null,
38 | });
39 | }
40 | }
41 |
42 | function removeObservable({ obs, observableList }) {
43 | const obsName = obs?.target?.id;
44 |
45 | if (observableList.has(obsName)) {
46 | observableList.set(obsName, {
47 | ...observableList.get(obsName),
48 | isPassed: true,
49 | });
50 | }
51 | }
52 |
53 | function colorize({ observableList, initialColor, fillColor, hasReverse }) {
54 | observableList.forEach((observable) => {
55 | if (!observable.isPassed) {
56 | const rect = observable.observable.target.getBoundingClientRect();
57 | const entry = observable?.observable;
58 |
59 | if (rect.bottom > halfScreenHeight && rect.top < halfScreenHeight) {
60 | if (initialColor && fillColor) {
61 | const depthPx = rect.bottom - halfScreenHeight;
62 | const depthPercent = (depthPx * 100) / rect.height;
63 | entry.target.style.background = `linear-gradient(to top, ${initialColor} ${depthPercent}%, ${fillColor} ${depthPercent}% 100%)`;
64 | entry.target.style.transform = "translateZ(0)";
65 | }
66 | }
67 |
68 | if (rect.bottom < halfScreenHeight) {
69 | if (initialColor && fillColor) {
70 | entry.target.style.background = fillColor;
71 | entry.target.style.transform = "unset";
72 | }
73 |
74 | if (observable?.callbackFn) {
75 | if (!observable?.callbackFired) {
76 | observable?.callbackFn();
77 |
78 | observable.callbackFired = true;
79 | }
80 | }
81 |
82 | if (!hasReverse) {
83 | removeObservable({
84 | obs: entry,
85 | observableList,
86 | });
87 | }
88 | }
89 |
90 | if (rect.top > halfScreenHeight && hasReverse) {
91 | entry.target.style.background = initialColor;
92 | }
93 | }
94 | });
95 | }
96 |
97 | const TimelineObserver = ({
98 | handleObserve,
99 | initialColor,
100 | fillColor,
101 | hasReverse,
102 | }: TimelineObserverProps) => {
103 | const observablesStore = useRef(new Map());
104 | const callbacks = useRef<{ [key: string]: () => void }>({});
105 |
106 | const callback = (entries) => {
107 | entries?.forEach((entry) => {
108 | if (entry.isIntersecting) {
109 | setObservable({
110 | obs: entry,
111 | observableList: observablesStore.current,
112 | callbacks: callbacks.current,
113 | });
114 | }
115 | });
116 | };
117 | const observer = useRef(new IntersectionObserver(callback, options));
118 |
119 | const animation = () => {
120 | window.requestAnimationFrame(() => {
121 | colorize({
122 | observableList: observablesStore.current,
123 | initialColor,
124 | fillColor,
125 | hasReverse,
126 | });
127 | });
128 | };
129 |
130 | useEffect(() => {
131 | document.addEventListener("scroll", animation);
132 | return () => {
133 | document.removeEventListener("scroll", animation);
134 | };
135 | }, []);
136 |
137 | const setObserver = (elem: HTMLElement, callbackFn?: () => void) => {
138 | const elemId = elem?.id;
139 |
140 | if (initialColor) {
141 | elem.style.background = initialColor;
142 | }
143 |
144 | observer.current.observe(elem);
145 |
146 | if (elemId && callbackFn) {
147 | callbacks.current[elemId] = callbackFn;
148 | }
149 | };
150 |
151 | return {handleObserve ? handleObserve(setObserver) : null}
;
152 | };
153 |
154 | export default TimelineObserver;
155 |
--------------------------------------------------------------------------------
/test/TimelineAnimation/TimelineAnimation.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef, useState } from "react";
2 |
3 | import TimelineObserver from "../../src";
4 |
5 | import "./styles.css";
6 |
7 | const Timeline = ({ setObserver, callback }) => {
8 | const [message1, setMessage1] = useState("");
9 | const [message2, setMessage2] = useState("");
10 | const [message3, setMessage3] = useState("");
11 |
12 | const timeline1 = useRef(null);
13 | const timeline2 = useRef(null);
14 | const timeline3 = useRef(null);
15 | const circle1 = useRef(null);
16 | const circle2 = useRef(null);
17 | const circle3 = useRef(null);
18 |
19 | const someCallback = () => {
20 | setMessage1("Step one");
21 | callback();
22 | };
23 |
24 | const someCallback2 = () => {
25 | setMessage2("Step two");
26 | };
27 |
28 | const someCallback3 = () => {
29 | setMessage3("Finish");
30 | };
31 |
32 | useEffect(() => {
33 | setObserver(timeline1.current);
34 | setObserver(timeline2.current);
35 | setObserver(timeline3.current);
36 | setObserver(circle1.current, someCallback);
37 | setObserver(circle2.current, someCallback2);
38 | setObserver(circle3.current, someCallback3);
39 | }, []);
40 |
41 | return (
42 |
43 |
44 |
45 |
46 | 1
47 |
48 |
{message1}
49 |
50 |
51 |
52 |
53 | 2
54 |
55 |
{message2}
56 |
57 |
58 |
59 |
60 | 3
61 |
62 |
{message3}
63 |
64 |
65 | );
66 | };
67 |
68 | export const TimelineAnimation = () => {
69 | const [message, setMessage] = useState("");
70 |
71 | const onCallback = () => {
72 | console.log("awesome");
73 | };
74 |
75 |
76 | return (
77 |
78 |
react-scroll-animation component
79 |
⬇️ scroll to start ⬇️
80 |
(
85 |
90 | )}
91 | />
92 | {message}
93 |
94 | );
95 | }
96 |
--------------------------------------------------------------------------------
/test/TimelineAnimation/index.js:
--------------------------------------------------------------------------------
1 | export { TimelineAnimation } from './TimelineAnimation'
--------------------------------------------------------------------------------
/test/TimelineAnimation/styles.css:
--------------------------------------------------------------------------------
1 | .App {
2 | font-family: sans-serif;
3 | text-align: center;
4 | }
5 |
6 | .wrapper {
7 | display: flex;
8 | flex-direction: column;
9 | align-items: center;
10 | }
11 |
12 | .timeline {
13 | height: 300px;
14 | width: 5px;
15 | background-color: #e5e5e5;
16 | }
17 |
18 | .stub1 {
19 | line-height: 300px;
20 | font-size: 24px;
21 | background-color: #eae4e4;
22 | }
23 |
24 | .stub2 {
25 | height: 500px;
26 | }
27 |
28 | .circle {
29 | width: 30px;
30 | height: 30px;
31 | display: inline-flex;
32 | align-items: center;
33 | justify-content: center;
34 | color: white;
35 | border-radius: 50%;
36 | background-color: #e5e5e5;
37 | }
38 |
39 | .circleWrapper {
40 | position: relative;
41 | }
42 |
43 | .message {
44 | position: absolute;
45 | top: 20%;
46 | left: 50%;
47 | min-width: 150px;
48 | font-weight: bold;
49 | }
50 |
--------------------------------------------------------------------------------
/test/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import { TimelineAnimation } from './TimelineAnimation/TimelineAnimation';
4 |
5 | ReactDOM.render(, document.querySelector('#root'));
--------------------------------------------------------------------------------
/tests/timeline.spec.ts:
--------------------------------------------------------------------------------
1 | const { test, expect } = require('@playwright/test');
2 |
3 | test('Check timeline background color after scroll', async ({ page }) => {
4 | await page.goto('http://localhost:3000/');
5 |
6 | const circle1Before = await page.locator('id=circle1')
7 | const circle1BeforeColor = await circle1Before.evaluate((e) => {
8 | return window.getComputedStyle(e).getPropertyValue("background-color")
9 | })
10 |
11 | await expect(circle1Before).toHaveCount(1)
12 | expect(circle1BeforeColor).toBe('rgb(229, 229, 229)')
13 |
14 | // Timeline
15 | const timeline1Before = await page.locator('id=timeline1')
16 | const timeline1BeforeColor = await timeline1Before.evaluate((e) => {
17 | return window.getComputedStyle(e).getPropertyValue("background-color")
18 | })
19 |
20 | await expect(timeline1Before).toHaveCount(1)
21 | expect(timeline1BeforeColor).toBe('rgb(229, 229, 229)')
22 |
23 |
24 | await expect(page.locator("text='Step one'")).toHaveCount(0)
25 | await expect(page.locator("text='Step two'")).toHaveCount(0)
26 | await expect(page.locator("text='Finish'")).toHaveCount(0)
27 |
28 | // Scroll
29 | await page.mouse.wheel(0, -100)
30 | await page.waitForTimeout(100);
31 | await page.mouse.wheel(0, 1100)
32 | await page.waitForTimeout(100);
33 |
34 | const circle1 = await page.locator('id=circle3')
35 | const circle1Color = await circle1.evaluate((e) => {
36 | return window.getComputedStyle(e).getPropertyValue("background-color")
37 | })
38 | const circle2 = await page.locator('id=circle3')
39 | const circle2Color = await circle2.evaluate((e) => {
40 | return window.getComputedStyle(e).getPropertyValue("background-color")
41 | })
42 | const circle3 = await page.locator('id=circle3')
43 | const circle3Color = await circle3.evaluate((e) => {
44 | return window.getComputedStyle(e).getPropertyValue("background-color")
45 | })
46 |
47 | await expect(circle1).toHaveCount(1)
48 | await expect(circle1Color).toBe('rgb(0, 0, 0)')
49 |
50 | await expect(circle2).toHaveCount(1)
51 | await expect(circle2Color).toBe('rgb(0, 0, 0)')
52 |
53 | await expect(circle3).toHaveCount(1)
54 | await expect(circle3Color).toBe('rgb(0, 0, 0)')
55 |
56 |
57 | // Timeline after scroll
58 |
59 | const timeline1 = await page.locator('id=timeline1')
60 | const timeline1Color = await circle1.evaluate((e) => {
61 | return window.getComputedStyle(e).getPropertyValue("background-color")
62 | })
63 | const timeline2 = await page.locator('id=timeline2')
64 | const timeline2Color = await timeline2.evaluate((e) => {
65 | return window.getComputedStyle(e).getPropertyValue("background-color")
66 | })
67 | const timeline3 = await page.locator('id=timeline3')
68 | const timeline3Color = await timeline3.evaluate((e) => {
69 | return window.getComputedStyle(e).getPropertyValue("background-color")
70 | })
71 |
72 | await expect(timeline1).toHaveCount(1)
73 | expect(timeline1Color).toBe('rgb(0, 0, 0)')
74 |
75 | await expect(timeline2).toHaveCount(1)
76 | expect(timeline2Color).toBe('rgb(0, 0, 0)')
77 |
78 | await expect(timeline3).toHaveCount(1)
79 | expect(timeline3Color).toBe('rgb(0, 0, 0)')
80 |
81 |
82 | await expect(page.locator("text='Step one'")).toHaveCount(1)
83 | await expect(page.locator("text='Step two'")).toHaveCount(1)
84 | await expect(page.locator("text='Finish'")).toHaveCount(1)
85 |
86 | // Expects the URL to contain intro.
87 | // await expect(page).toHaveURL(/.*reactjs/);
88 | });
89 |
90 | // test('Check timeline callbacks', async ({ page }) => {
91 | // await page.goto('http://localhost:3000/')
92 |
93 | // await expect(page.locator("text='Step one'")).toHaveCount(0)
94 | // await expect(page.locator("text='Step two'")).toHaveCount(0)
95 | // await expect(page.locator("text='Finish'")).toHaveCount(0)
96 |
97 | // // Scroll
98 | // await page.mouse.wheel(0, -100)
99 | // await page.mouse.wheel(0, 1100)
100 | // await page.waitForTimeout(500);
101 |
102 | // await expect(page.locator("text='Step one'")).toHaveCount(1)
103 | // await expect(page.locator("text='Step two'")).toHaveCount(1)
104 | // await expect(page.locator("text='Finish'")).toHaveCount(1)
105 | // })
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "allowSyntheticDefaultImports": true,
4 | "noImplicitAny": false,
5 | "outDir": "dist",
6 | "module": "esnext",
7 | "target": "es5",
8 | "lib": ["es6", "dom", "es2016", "es2017"],
9 | "sourceMap": true,
10 | "allowJs": false,
11 | "jsx": "react",
12 | "declaration": true,
13 | "moduleResolution": "node",
14 | "forceConsistentCasingInFileNames": true,
15 | "noImplicitReturns": true,
16 | "noImplicitThis": true,
17 | "strictNullChecks": true,
18 | "suppressImplicitAnyIndexErrors": true,
19 | "noUnusedLocals": true,
20 | "noUnusedParameters": true
21 | },
22 | "include": ["src"],
23 | "exclude": ["node_modules", "dist", "example", "rollup.config.js"]
24 | }
--------------------------------------------------------------------------------