├── .browserslist
├── .changeset
├── README.md
└── config.json
├── .editorconfig
├── .eslintignore
├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── BUG_REPORT.md
│ ├── ENHANCEMENT.md
│ └── FEATURE_REQUEST.md
├── PULL_REQUEST_TEMPLATE.md
└── workflows
│ ├── release.yml
│ ├── stale.yml
│ └── tests.yml
├── .gitignore
├── .npmignore
├── .npmrc
├── .prettierignore
├── .prettierrc.js
├── .storybook
└── main.js
├── CHANGELOG.md
├── LICENSE
├── README.md
├── babel.config.js
├── commitlint.config.js
├── cypress.json
├── cypress
├── integration
│ ├── load_animation.spec.js
│ ├── player_controls.spec.js
│ ├── player_methods.spec.js
│ └── player_properties.spec.js
├── plugins
│ └── index.js
├── react-testing-pages
│ ├── .eslintignore
│ ├── .gitignore
│ ├── README.md
│ ├── craco.config.js
│ ├── package.json
│ ├── public
│ │ ├── animation.lottie.json
│ │ ├── index.html
│ │ ├── manifest.json
│ │ └── robots.txt
│ ├── src
│ │ ├── App.css
│ │ ├── App.js
│ │ ├── App.test.js
│ │ ├── components
│ │ │ ├── App.css
│ │ │ ├── Controls.js
│ │ │ ├── Home.js
│ │ │ ├── Load.js
│ │ │ ├── Methods.js
│ │ │ └── Properties.js
│ │ ├── index.css
│ │ ├── index.js
│ │ ├── logo.svg
│ │ ├── reportWebVitals.js
│ │ └── setupTests.js
│ ├── webpack.config.js
│ └── yarn.lock
└── support
│ ├── commands.js
│ └── index.js
├── package.json
├── release.config.js
├── rollup.config.js
├── src
├── ColorPicker.tsx
├── Controls.tsx
├── Player.tsx
├── Popover.tsx
├── Seeker.tsx
├── Styles.css
├── index.ts
└── versions.ts
├── stories
└── index.stories.tsx
├── tsconfig.json
└── yarn.lock
/.browserslist:
--------------------------------------------------------------------------------
1 | > 2%
2 | not dead
3 |
--------------------------------------------------------------------------------
/.changeset/README.md:
--------------------------------------------------------------------------------
1 | # Changesets
2 |
3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
4 | with multi-package repos, or single-package repos to help you version and publish your code. You can
5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets)
6 |
7 | We have a quick list of common questions to get you started engaging with this project in
8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
9 |
--------------------------------------------------------------------------------
/.changeset/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://unpkg.com/@changesets/config@1.7.0/schema.json",
3 | "changelog": "@changesets/cli/changelog",
4 | "commit": false,
5 | "fixed": [],
6 | "linked": [],
7 | "access": "public",
8 | "baseBranch": "master",
9 | "updateInternalDependencies": "patch",
10 | "ignore": []
11 | }
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | charset = utf-8
5 | end_of_line = lf
6 | indent_size = 2
7 | indent_style = space
8 | insert_final_newline = true
9 | trim_trailing_whitespace = true
10 |
11 | [*.md]
12 | trim_trailing_whitespace = false
--------------------------------------------------------------------------------
/.eslintignore:
--------------------------------------------------------------------------------
1 | # Third party
2 | **/node_modules
3 |
4 | # Build products
5 | docs/
6 | dist/
7 |
8 | # Testing
9 | coverage/
10 | example/
11 | cypress/
12 | cypress/react-testing-pages/src/components/lottie-react.js
13 |
14 | stories/
15 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: { browser: true },
3 | parser: '@typescript-eslint/parser',
4 | extends: [
5 | 'plugin:@typescript-eslint/recommended', // Uses the recommended rules from @typescript-eslint/eslint-plugin
6 | 'plugin:prettier/recommended', // Enables eslint-config-prettier and eslint-plugin-prettier
7 | 'plugin:react/recommended', // Uses the recommended rules from eslint-plugin-react
8 | 'plugin:react-hooks/recommended', // Uses the recommended rules from eslint-plugin-react-hooks
9 | 'plugin:jsx-a11y/recommended', // Uses the recommended rules from eslint-plugin-jsx-a11y
10 | 'prettier/react', // Disable rules that would conflict with prettier
11 | ],
12 | plugins: ['@typescript-eslint', 'simple-import-sort'],
13 | rules: {
14 | // Disable sorting rules to not conflict with simple-import-sort
15 | 'sort-imports': 'off',
16 |
17 | // Enable the simple-import-sort rule
18 | 'simple-import-sort/sort': 'error',
19 |
20 | // Disable some preference rules
21 | '@typescript-eslint/no-explicit-any': 'off',
22 | '@typescript-eslint/interface-name-prefix': 'off',
23 | '@typescript-eslint/explicit-function-return-type': 'off',
24 | 'react/prop-types': 'off',
25 | },
26 | overrides: [
27 | {
28 | files: ['*.js'],
29 | rules: {
30 | '@typescript-eslint/no-var-requires': 'off',
31 | },
32 | },
33 | ],
34 | parserOptions: {
35 | ecmaVersion: 2018, // Allows for the parsing of modern ECMAScript features
36 | sourceType: 'module', // Allows for the use of imports
37 | ecmaFeatures: {
38 | jsx: true,
39 | },
40 |
41 | // typescript-eslint specific options
42 | warnOnUnsupportedTypeScriptVersion: true,
43 | },
44 | settings: {
45 | react: {
46 | version: 'detect', // React version. "detect" automatically picks the version you have installed.
47 | },
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/BUG_REPORT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '🐛 Bug Report'
3 | about: Something isn't working
4 | ---
5 |
6 | ## Overview
7 |
8 | ...
9 |
10 | ## Consuming repo
11 |
12 | > What repo were you working in when this issue occurred?
13 |
14 | ...
15 |
16 | ## Labels
17 |
18 | - [ ] Add the `Type: Bug` label to this issue.
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ENHANCEMENT.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '📈 Enhancement'
3 | about: Enhancement to our codebase that isn't a adding or changing a feature
4 | ---
5 |
6 | ## Overview
7 |
8 | ...
9 |
10 | ## Motivation
11 |
12 | > What inspired this enhancement? What makes you think this should be included?
13 |
14 | ...
15 |
16 | ## Labels
17 |
18 | - [ ] Add the `Type: Enhancement` label to this issue.
19 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: '🙌 Feature Request'
3 | about: Suggest a new feature, or changes to an existing one
4 | ---
5 |
6 | ## Overview
7 |
8 | ...
9 |
10 | ## Type
11 |
12 | - [ ] New feature
13 | - [ ] Changes to existing features
14 |
15 | ## Motivation
16 |
17 | > What inspired this feature request? What problems were you facing, or what else makes you think this should be
18 | > included?
19 |
20 | ...
21 |
22 | ## Labels
23 |
24 | - [ ] Add the `Type: Feature Request` label to this issue.
25 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 | ## Description
2 |
3 |
8 |
9 | ## Type of change
10 |
11 |
14 |
15 | - [ ] Patch: Bug (non-breaking change which fixes an issue)
16 | - [ ] Minor: New feature (non-breaking change which adds functionality)
17 | - [ ] Major: Breaking change (fix or feature that would cause existing functionality to not work as expected)
18 |
19 | ## Checklist
20 |
21 | - [ ] This is something we need to do.
22 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: Release
2 |
3 | on:
4 | push:
5 | branches: [master, beta]
6 |
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | timeout-minutes: 15
11 |
12 | strategy:
13 | matrix:
14 | node-version: [14.x]
15 |
16 | steps:
17 | - name: Checkout
18 | uses: actions/checkout@v4
19 | with:
20 | fetch-depth: 0
21 | - name: Cache NPM
22 | uses: actions/cache@v4
23 | env:
24 | cache-name: cache-npm
25 | with:
26 | path: ~/.npm
27 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn-lock.json') }}
28 | restore-keys: |
29 | ${{ runner.os }}-build-${{ env.cache-name }}-
30 | - name: Setup Node.js ${{ matrix.node-version }}
31 | uses: actions/setup-node@v4
32 | with:
33 | node-version: ${{ matrix.node-version }}
34 | registry-url: 'https://npm.pkg.github.com'
35 | - name: Install dependencies
36 | run: yarn install | cd ./cypress/react-testing-pages/ && yarn
37 | env:
38 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
39 |
40 | # - name: Run tests
41 | # continue-on-error: false
42 | # run: yarn run start-and-run-tests
43 | # env:
44 | # NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
45 | - name: Run linting
46 | run: yarn lint
47 | env:
48 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
49 | - name: Bump version
50 | run: yarn run version
51 | env:
52 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53 | - name: Build project
54 | run: yarn build
55 |
56 | release-npm:
57 | needs: build
58 | name: Release npm
59 | runs-on: ubuntu-latest
60 | steps:
61 | - uses: actions/checkout@v4
62 | - uses: actions/setup-node@v4
63 | with:
64 | node-version: ${{ matrix.node-version }}
65 | registry-url: https://registry.npmjs.org/
66 | - run: yarn install --frozen-lockfile
67 | - run: npm publish --access public
68 | env:
69 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
70 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
71 | NPMJS_TOKEN: ${{ secrets.NPMJS_TOKEN }}
72 | GIT_COMMIT: ${{ github.sha }}
73 | GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
74 | GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
75 | GIT_COMMITTER_NAME: ${{ secrets.GIT_COMMITTER_NAME }}
76 | GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }}
77 |
78 | release-gpr:
79 | needs: build
80 | name: Release gpr
81 | runs-on: ubuntu-latest
82 | steps:
83 | - uses: actions/checkout@v4
84 | - uses: actions/setup-node@v4
85 | with:
86 | node-version: ${{ matrix.node-version }}
87 | registry-url: https://npm.pkg.github.com/
88 | scope: '@lottiefiles'
89 | - run: yarn install --frozen-lockfile
90 | - run: echo "//npm.pkg.github.com:_authToken=${{ secrets.GITHUB_TOKEN }}" > ~/.npmrc
91 | - run: npm publish --@lottiefiles:registry=https://npm.pkg.github.com/
92 | env:
93 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
94 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
95 | NPMJS_TOKEN: ${{ secrets.NPMJS_TOKEN }}
96 | GIT_COMMIT: ${{ github.sha }}
97 | GIT_AUTHOR_NAME: ${{ secrets.GIT_AUTHOR_NAME }}
98 | GIT_AUTHOR_EMAIL: ${{ secrets.GIT_AUTHOR_EMAIL }}
99 | GIT_COMMITTER_NAME: ${{ secrets.GIT_COMMITTER_NAME }}
100 | GIT_COMMITTER_EMAIL: ${{ secrets.GIT_COMMITTER_EMAIL }}
101 |
--------------------------------------------------------------------------------
/.github/workflows/stale.yml:
--------------------------------------------------------------------------------
1 | name: 'Close stale issues'
2 | on:
3 | schedule:
4 | - cron: '30 1 * * *'
5 |
6 | jobs:
7 | stale:
8 | runs-on: ubuntu-latest
9 | steps:
10 | - uses: actions/stale@v3
11 | with:
12 | repo-token: ${{ secrets.GITHUB_TOKEN }}
13 | # Idle number of days before marking an issue/pr as stale.
14 | days-before-stale: 60
15 | # Idle number of days before closing an stale issue/pr.
16 | # Set to -1 to never automatically close stale issues.
17 | days-before-close: -1
18 | # Message to post on the stale issue.
19 | stale-issue-message:
20 | 'This issue has been automatically marked as stale because it has not had recent activity. It will be closed
21 | in 7 days if no further activity occurs.'
22 | # Label to apply on the stale issue
23 | stale-issue-label: 'stale'
24 | # Labels on an issue exempted from being marked as stale.
25 | # Set to #wip for work-in-progress:
26 | exempt-issue-labels: 'wip'
27 | # Message to post on the stale pr.
28 | stale-pr-message:
29 | 'This rull request has been automatically marked as stale because it has not had recent activity. It will be
30 | closed in 7 days if no further activity occurs.'
31 | # Label to apply on the stale pr.
32 | stale-pr-label: 'stale'
33 | # Labels on a pr exempted from being marked as stale.
34 | # Set to #wip for work-in-progress:
35 | exempt-pr-labels: 'wip'
36 | # Whether to remove stale label from issue/pr on updates or comments.
37 | remove-stale-when-updated: true
38 |
--------------------------------------------------------------------------------
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: Run tests
2 |
3 | on:
4 | pull_request:
5 | branches: [master, main, beta]
6 |
7 | jobs:
8 | tests:
9 | name: Tests
10 |
11 | runs-on: ubuntu-latest
12 |
13 | strategy:
14 | matrix:
15 | node-version: [12.x]
16 |
17 | steps:
18 | - name: Checkout
19 | uses: actions/checkout@v4
20 | with:
21 | fetch-depth: 0
22 |
23 | - name: Cache NPM
24 | uses: actions/cache@v4
25 | env:
26 | cache-name: cache-npm
27 | with:
28 | path: ~/.npm
29 | key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/yarn-lock.json') }}
30 | restore-keys: |
31 | ${{ runner.os }}-build-${{ env.cache-name }}-
32 | - name: Setup Node.js ${{ matrix.node-version }}
33 | uses: actions/setup-node@v4
34 | with:
35 | node-version: ${{ matrix.node-version }}
36 | registry-url: 'https://npm.pkg.github.com'
37 |
38 | - name: Install dependencies
39 | run: yarn install
40 | env:
41 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
42 |
43 | - name: Run linting
44 | run: yarn lint
45 | env:
46 | NODE_AUTH_TOKEN: ${{ secrets.NPMJS_TOKEN }}
47 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 |
8 | # Runtime data
9 | pids
10 | *.pid
11 | *.seed
12 | *.pid.lock
13 |
14 | # node-waf configuration
15 | .lock-wscript
16 |
17 | # Dependency directories
18 | **/node_modules/
19 |
20 | # Force Karam to not add package-lock.json
21 | package-lock.json
22 |
23 | # Optional npm cache directory
24 | .npm
25 |
26 | # Optional eslint cache
27 | .eslintcache
28 |
29 | # Yarn Integrity file
30 | .yarn-integrity
31 |
32 | # dotenv environment variables file
33 | .env
34 | *.env
35 |
36 | # IDEs
37 | **/.idea
38 | **/.vscode
39 | **/.history
40 |
41 | # Operating system
42 | .DS_Store
43 | .tmp
44 |
45 | # Dist artifacts
46 | dist/
47 |
48 | # Tests
49 | coverage/
50 | cypress/react-testing-pages/src/components/lottie-react.js
51 | .nyc_output
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # IDE
2 | .vscode
3 | .idea
4 | .history
5 | *.log
6 |
7 | .storybook
8 | stories
9 |
10 | # CI/CD
11 | .travis.yml
12 |
13 | # Configs
14 | jest.config.js
15 | rollup.config.js
16 | tsconfig.json
17 |
18 | # Docs
19 | docs/
20 | examples/
21 | cypress/
22 | cypress/react-testing-pages/src/components/lottie-react.js
23 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | @lottiefiles:registry=https://registry.npmjs.org/
2 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/*
2 | package.json
3 | package-lock.json
4 | yarn.lock
5 | cypress/
6 | cypress/react-testing-pages/src/components/lottie-react.js
7 | stories/
8 | src/versions.d.ts
9 |
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | arrowParens: 'avoid',
3 | bracketSpacing: true,
4 | endOfLine: 'lf',
5 | insertPragma: false,
6 | jsxBracketSameLine: false,
7 | printWidth: 120,
8 | proseWrap: 'always',
9 | requirePragma: false,
10 | semi: true,
11 | singleQuote: true,
12 | tabWidth: 2,
13 | trailingComma: 'all',
14 | useTabs: false,
15 | overrides: [
16 | {
17 | files: '*.json',
18 | options: {
19 | tabWidth: 2,
20 | singleQuote: false
21 | }
22 | }
23 | ]
24 | };
--------------------------------------------------------------------------------
/.storybook/main.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | module.exports = {
3 | webpackFinal: async config => {
4 | config.module.rules.push({
5 | test: /\.(ts|tsx)$/,
6 | use: [
7 | {
8 | loader: require.resolve('ts-loader'),
9 | },
10 | // Optional
11 | {
12 | loader: require.resolve('react-docgen-typescript-loader'),
13 | },
14 | ],
15 | });
16 | config.module.rules.push({
17 | test: /\.scss$/,
18 | use: ['style-loader', 'css-loader', 'sass-loader'],
19 | include: path.resolve(__dirname, '../'),
20 | });
21 |
22 | config.resolve.extensions.push('.ts', '.tsx');
23 | return config;
24 | },
25 | stories: ['../**/*.stories.tsx'],
26 | addons: ['@storybook/preset-typescript', '@storybook/addon-actions', '@storybook/addon-links'],
27 | };
28 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## 3.6.0
4 |
5 | ### Minor Changes
6 |
7 | - 0589acd: chore: 🤖 upgrade React to v19.0.0
8 |
9 | ## 3.5.4
10 |
11 | ### Patch Changes
12 |
13 | - b40ce3d: chore: 🤖 upgrade lottie-web v5.12.2
14 |
15 | ## 3.5.3
16 |
17 | ### Patch Changes
18 |
19 | - bumped lottie-web
20 |
21 | ## 3.5.2
22 |
23 | ### Patch Changes
24 |
25 | - d71a178: added instanceloaded event
26 |
27 | ## 3.5.1
28 |
29 | ### Patch Changes
30 |
31 | - f807fde: re-arranged events to fire when internal state is set
32 |
33 | ## 3.5.0
34 |
35 | ### Minor Changes
36 |
37 | - 978552b: added getVersions(), updated lottie-web
38 |
39 | ## 3.4.9
40 |
41 | ### Patch Changes
42 |
43 | - 730c3b2: add aria-label to Controls
44 | - 32e0728: fix double animation with react 18 strict mode
45 | - cd94951: chore: version bump
46 |
47 | ## 3.4.6
48 |
49 | ### Patch Changes
50 |
51 | - 8d24ecd: changes type for the renderer variable
52 | - fixed source-maps
53 |
54 | ## 3.4.3
55 |
56 | ### Patch Changes
57 |
58 | - bumping package version to correct number
59 |
60 | ## 3.3.1
61 |
62 | ### Patch Changes
63 |
64 | - added changesets All notable changes to this project will be documented in this file. The format is based on
65 | [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to
66 | [Semantic Versioning](https://semver.org/spec/v2.0.0.html), enforced with
67 | [semantic-release](https://github.com/semantic-release/semantic-release).
68 |
69 | ## [3.4.2](https://github.com/LottieFiles/lottie-react/compare/v3.4.1...v3.4.2) (2022-02-22)
70 |
71 | ### Bug Fixes
72 |
73 | - fixed speed affecting all lotties on a page
74 | ([92cec07](https://github.com/LottieFiles/lottie-react/commit/92cec07ce0fb3239883179c8a4fd344806cc29ba))
75 |
76 | ## [3.4.1](https://github.com/LottieFiles/lottie-react/compare/v3.4.0...v3.4.1) (2021-08-27)
77 |
78 | ### Bug Fixes
79 |
80 | - **player:** added null check on window
81 | ([b1498ae](https://github.com/LottieFiles/lottie-react/commit/b1498ae73aa56d5ff392120e9935b09cdad3df56))
82 |
83 | # [3.4.0](https://github.com/LottieFiles/lottie-react/compare/v3.3.1...v3.4.0) (2021-08-27)
84 |
85 | ### Features
86 |
87 | - **window:** added lottie to the window object
88 | ([f587f7f](https://github.com/LottieFiles/lottie-react/commit/f587f7f2ad16aa8b58119aaac94e7c777354edf9))
89 |
90 | ## [3.3.1](https://github.com/LottieFiles/lottie-react/compare/v3.3.0...v3.3.1) (2021-05-24)
91 |
92 | ### Bug Fixes
93 |
94 | - **onhover bugfix:** onhover bug fixz
95 | ([2bfa12b](https://github.com/LottieFiles/lottie-react/commit/2bfa12b38482c9dd992bfcf738c2a7a684fd1aca))
96 |
97 | # [3.3.0](https://github.com/LottieFiles/lottie-react/compare/v3.2.0...v3.3.0) (2021-05-18)
98 |
99 | ### Features
100 |
101 | - **player core:** added onhover function
102 | ([a2dc06c](https://github.com/LottieFiles/lottie-react/commit/a2dc06c1d60535fcd834da45c0611dbdece147ed))
103 |
104 | # [3.2.0](https://github.com/LottieFiles/lottie-react/compare/v3.1.4...v3.2.0) (2021-05-10)
105 |
106 | ### Features
107 |
108 | - **packages update:** testing build pipeline. CI/CD
109 | ([5cb47ff](https://github.com/LottieFiles/lottie-react/commit/5cb47ff9f6f02873afcbeaf004dbf23ef556ad2c))
110 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 LottieFiles.com
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## LottiePlayer React Component
2 |
3 | This is a React component for the Lottie Web Player
4 |
5 | ## Demo
6 |
7 | 
8 |
9 | ## Documentation
10 |
11 | - [View documentation](https://docs.lottiefiles.com/lottie-player/components/lottie-react)
12 |
13 | #### In Javascript or TypeScript:
14 |
15 | 1. Install package using npm or yarn.
16 |
17 | ```shell
18 | npm install --save @lottiefiles/react-lottie-player
19 | ```
20 |
21 | 2. Import package in your code.
22 |
23 | ```javascript
24 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
25 | ```
26 |
27 | ## Example/Development
28 |
29 | 1. Clone repo
30 |
31 | 2. run yarn install
32 |
33 | 3. run yarn storybook
34 |
35 | ```shell
36 | yarn storybook
37 | ```
38 |
39 | ## Usage
40 |
41 | ### Player component
42 |
43 | Add the element `Player` and set the `src` prop to a URL pointing to a valid Lottie JSON.
44 |
45 | ```javascript
46 |
52 |
53 |
54 | ```
55 |
56 | ## Props
57 |
58 | | Prop | Description | Type | Default |
59 | | -------------------- | ---------------------------------------------------------------------- | ------------------ | ----------- |
60 | | `lottieRef` | Get lottie animation object | `function` | `undefined` |
61 | | `onEvent` | Listen for events | `function` | `undefined` |
62 | | `onStateChange` | Play state changes | `function` | `undefined` |
63 | | `onBackgroundChange` | Listen for bg changes | `function` | `undefined` |
64 | | `autoplay` | Autoplay animation on load. | `boolean` | `false` |
65 | | `background` | Background color. | `string` | `undefined` |
66 | | `controls` | Show controls. | `boolean` | `false` |
67 | | `direction` | Direction of animation. | `number` | `1` |
68 | | `hover` | Whether to play on mouse hover. | `boolean` | `false` |
69 | | `keepLastFrame` | Stop animation on the last frame.Has no effect if `loop` is true. | `boolean` | `false` |
70 | | `loop` | Whether to loop animation. | `boolean` | `false` |
71 | | `renderer` | Renderer to use. | `"svg" | "canvas"` | `'svg'` |
72 | | `speed` | Animation speed. | `number` | `1` |
73 | | `style` | The style for the container. | `object` | `undefined` |
74 | | `src` _(required)_ | Bodymovin JSON data or URL to JSON. | `object` | `string`| `undefined` |
75 |
76 | ## Get Player instance
77 |
78 | To call methods on the instance of the Player component. you may get a reference to the component and call the methods
79 | on ref.current. This is esentially reacts way of doing a document.getElementById(); You may then use this ref ie: player
80 | in the example below to call methods that are described in this documentation. See ref in
81 | [react documentation](https://reactjs.org/docs/refs-and-the-dom.html)
82 |
83 | ```javascript
84 | import React from 'react';
85 | import { Player } from '@lottiefiles/react-lottie-player';
86 |
87 | class App extends React.Component {
88 | constructor(props) {
89 | super(props);
90 | this.player = React.createRef(); // initialize your ref
91 | }
92 | render() {
93 | return (
94 |
102 | );
103 | }
104 | }
105 |
106 | export default App;
107 | ```
108 |
109 | ## Get Lottie instance
110 |
111 | The lottieRef prop returns the Lottie instance which you can use to set data and call methods as described in the
112 | [bodymovin documentation](https://github.com/airbnb/lottie-web).
113 |
114 | ```javascript
115 | import React from 'react';
116 | import { Player } from '@lottiefiles/react-lottie-player';
117 |
118 | class App extends React.Component {
119 | constructor(props) {
120 | super(props);
121 | this.state = { lottie: null }; // initialize your state
122 | }
123 |
124 | render() {
125 | return (
126 | {
128 | this.setState({ lottie: instance }); // the lottie instance is returned in the argument of this prop. set it to your local state
129 | }}
130 | autoplay={false}
131 | loop={true}
132 | controls={true}
133 | src="https://assets3.lottiefiles.com/packages/lf20_XZ3pkn.json"
134 | style={{ height: '300px', width: '300px' }}
135 | >
136 | );
137 | }
138 | }
139 |
140 | export default App;
141 | ```
142 |
143 | ## Listening for events
144 |
145 | ```javascript
146 | import React from 'react';
147 | import { Player } from '@lottiefiles/react-lottie-player';
148 |
149 | class App extends React.Component {
150 | constructor(props) {
151 | super(props);
152 | this.player = React.createRef();
153 | }
154 |
155 | doSomething() {
156 | this.player.current.play(); // make use of the player and call methods
157 | }
158 |
159 | render() {
160 | return (
161 | {
163 | if (event === 'load') this.doSomething(); // check event type and do something
164 | }}
165 | ref={this.player}
166 | autoplay={false}
167 | loop={true}
168 | controls={true}
169 | src="https://assets3.lottiefiles.com/packages/lf20_XZ3pkn.json"
170 | style={{ height: '300px', width: '300px' }}
171 | >
172 | );
173 | }
174 | }
175 |
176 | export default App;
177 | ```
178 |
179 | ## Events
180 |
181 | The following events are exposed and can be listened to via `addEventListener` calls.
182 |
183 | | Name | Description |
184 | | ---------- | ------------------------------------------------------------------------- |
185 | | `load` | Animation data is loaded. |
186 | | `error` | An animation source cannot be parsed, fails to load or has format errors. |
187 | | `ready` | Animation data is loaded and player is ready. |
188 | | `play` | Animation starts playing. |
189 | | `pause` | Animation is paused. |
190 | | `stop` | Animation is stopped. |
191 | | `freeze` | Animation is paused due to player being invisible. |
192 | | `loop` | An animation loop is completed. |
193 | | `complete` | Animation is complete (all loops completed). |
194 | | `frame` | A new frame is entered. |
195 |
196 | ## Methods
197 |
198 | ### `pause() => void`
199 |
200 | Pause animation play.
201 |
202 | #### Returns
203 |
204 | Type: `void`
205 |
206 | ### `play() => void`
207 |
208 | Start playing animation.
209 |
210 | #### Returns
211 |
212 | Type: `void`
213 |
214 | ### `setPlayerDirection(direction: 1 | -1 ) => void`
215 |
216 | Animation play direction.
217 |
218 | #### Parameters
219 |
220 | | Name | Type | Description |
221 | | ------- | -------- | ----------------- |
222 | | `value` | `number` | Direction values. |
223 |
224 | #### Returns
225 |
226 | Type: `void`
227 |
228 | ### `setPlayerSpeed(speed?: number) => void`
229 |
230 | Sets animation play speed.
231 |
232 | #### Parameters
233 |
234 | | Name | Type | Description |
235 | | ------- | -------- | --------------- |
236 | | `value` | `number` | Playback speed. |
237 |
238 | #### Returns
239 |
240 | Type: `void`
241 |
242 | ### `stop() => void`
243 |
244 | Stops animation play.
245 |
246 | #### Returns
247 |
248 | Type: `void`
249 |
250 | ### `setSeeker(frame: number, play: boolean) => void`
251 |
252 | Seek to a given frame.
253 |
254 | #### Returns
255 |
256 | Type: `void`
257 |
258 | ## Contributing
259 |
260 | We use changesets to maintain a changelog for this repository. When making any change to the codebase that impacts functionality or performance we require a changeset to be present.
261 |
262 | To add a changeset run:
263 |
264 | ```
265 | yarn run changeset
266 | ```
267 |
268 | And select the type of version bump you'd like (major, minor, path).
269 |
270 | You can document the change in detail and format it properly using Markdown by opening the ".md" file that the "yarn changeset" command created in the ".changeset" folder. Open the file, it should look something like this:
271 |
272 | ```
273 | ---
274 | "@lottiefiles/pkg1": minor
275 | "@lottiefiles/pkg2": major
276 | ---
277 |
278 | This is where you document your **changes** using Markdown.
279 |
280 | - You can write
281 | - However you'd like
282 | - In as much detail as you'd like
283 |
284 | Aim to provide enough details so that team mates and future you can understand the changes and the context of the change.
285 | ```
286 |
287 | You can commit your changes and the changeset to your branch and then create a pull request on the develop branch.
288 |
289 | ## License
290 |
291 | MIT License © LottieFiles.com
292 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | const plugins = [];
2 | if (process.env.NODE_ENV === 'test') plugins.push('istanbul');
3 |
4 | module.exports = function(api) {
5 | api.cache(true);
6 |
7 | return {
8 | presets: [
9 | '@babel/preset-typescript',
10 | [
11 | '@babel/preset-env',
12 | {
13 | targets: {
14 | edge: '17',
15 | firefox: '60',
16 | chrome: '67',
17 | safari: '11.1',
18 | },
19 | },
20 | ],
21 | ],
22 | plugins,
23 | };
24 | };
25 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2020 Design Barn Inc.
3 | */
4 |
5 | module.exports = { extends: ['@commitlint/config-conventional'] };
6 |
--------------------------------------------------------------------------------
/cypress.json:
--------------------------------------------------------------------------------
1 | {
2 | "video": false,
3 | "screenshotOnRunFailure": false
4 | }
--------------------------------------------------------------------------------
/cypress/integration/load_animation.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 Design Barn Inc.
3 | */
4 |
5 | context("Player component DOM check", () => {
6 | beforeEach(() => {
7 | cy.visit("http://localhost:8080/load");
8 | });
9 |
10 | it('Loads an animation on page.', () => {
11 | cy.get("#player-one").should("have.length", 1);
12 | });
13 |
14 | it('Checks that loading with an empty URL displays an error.', function () {
15 | cy.get("#container-two .lf-error")
16 | .should("have.class", "lf-error");
17 | });
18 |
19 | it('Loads an animation with invalid url.', function () {
20 | cy.get("#container-three .lf-error")
21 | .should("have.class", "lf-error");
22 | });
23 |
24 | it("Looks for lf-player-container class", () => {
25 | cy.get("#container-one .lf-player-container")
26 | .should("have.class", "lf-player-container");
27 | });
28 |
29 | it("Looks lf-player-controls class", () => {
30 | cy.get("#container-one .lf-player-controls")
31 | .should("have.class", "lf-player-controls");
32 | });
33 |
34 | it.skip("Looks inside shadow-dom for aria-label", () => {
35 | cy.get("#player-one lottie-player")
36 | .shadow()
37 | .find("#animation-container")
38 | .should("have.attr", "aria-label");
39 | });
40 |
41 | it("Verifies controls", () => {
42 | cy.get("#container-one")
43 | .find(".lf-player-controls")
44 | cy.get("#container-one")
45 | .find(".lf-player-btn").should('have.length', 3)
46 | });
47 | });
48 |
--------------------------------------------------------------------------------
/cypress/integration/player_controls.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 Design Barn Inc.
3 | */
4 |
5 | import Lottie from "lottie-web";
6 |
7 | context("Player controls", () => {
8 | beforeEach(() => {
9 | cy.visit("http://localhost:8080/controls");
10 | });
11 |
12 | it("clicks on play button and verifies animation is playing", function (done) {
13 | cy.wait(3000);
14 | cy.get("#container-one .lf-player-btn").eq(0).click({force: true});
15 |
16 | cy.window().then( (win) => {
17 | if (!win.lottie.getRegisteredAnimations()[0].isPaused)
18 | done();
19 | });
20 | });
21 |
22 | it("clicks on pause button and verifies animation is paused", function (done) {
23 | cy.wait(3000);
24 | cy.get("#container-two .lf-player-btn").eq(0).click({force: true});
25 |
26 | cy.window().then( (win) => {
27 | if (win.lottie.getRegisteredAnimations()[1].isPaused)
28 | done();
29 | });
30 | });
31 |
32 | it("clicks on stop button and verififes animation is stopped and at frame 0", function (done) {
33 | cy.wait(3000);
34 | cy.get("#container-three .lf-player-btn").eq(1).click({force: true});
35 |
36 | cy.window().then( (win) => {
37 | if (win.lottie.getRegisteredAnimations()[2].isPaused &&
38 | win.lottie.getRegisteredAnimations()[2].currentFrame === 0)
39 | done();
40 | });
41 | });
42 |
43 | it("clicks on loop button and verififes animation loops", function (done) {
44 | cy.get("#container-four .lf-player-btn").eq(2).click();
45 |
46 | cy.wait(3000);
47 | cy.window().then( (win) => {
48 | if (!win.lottie.getRegisteredAnimations()[3].loop || win.lottie.getRegisteredAnimations()[3].playCount > 1)
49 | done();
50 | });
51 | });
52 |
53 | it.skip("clicks on color background choice and verifies background color", function () {
54 | cy.get("#player-five #lottie-controls").find("#lottie-bg-picker-button").click();
55 | cy.get("#player-five #lottie-controls").find("#lottie-color-choice-4").click();
56 | cy.get("#player-five lottie-player")
57 | .should("have.css", "background-color")
58 | .and("eq", "rgb(58, 146, 218)");
59 | });
60 | });
61 |
--------------------------------------------------------------------------------
/cypress/integration/player_methods.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 Design Barn Inc.
3 | */
4 |
5 | context("Player methods", () => {
6 | beforeEach(() => {
7 | cy.visit("http://localhost:8080/methods");
8 | });
9 |
10 | it("Player-one should have play toggled.", function (done) {
11 | cy.wait(3000);
12 | cy.window().then( (win) => {
13 | if (!win.lottie.getRegisteredAnimations()[0].isPaused)
14 | done();
15 | });
16 | });
17 |
18 | it.skip("Player-two should have loop toggled.", function (done) {
19 | cy.wait(3000);
20 | cy.window().then( (win) => {
21 | if (!win.lottie.getRegisteredAnimations()[1].loop)
22 | done();
23 | });
24 | });
25 |
26 | it("Player-three should play at x5 the speed", function(done) {
27 | cy.wait(3000);
28 | cy.window().then( (win) => {
29 | if (win.lottie.getRegisteredAnimations()[2].playSpeed === 5)
30 | done();
31 | });
32 | });
33 |
34 | it.skip("Player-four Should have a green background.", () => {
35 | cy.get("#player-four lottie-player")
36 | .should("have.css", "background-color")
37 | .and("eq", "rgb(0, 255, 107)");
38 | });
39 |
40 | it("Player-five should be paused.", function (done) {
41 | cy.wait(3000);
42 | cy.window().then( (win) => {
43 | if (win.lottie.getRegisteredAnimations()[4].isPaused)
44 | done();
45 | });
46 | });
47 | });
--------------------------------------------------------------------------------
/cypress/integration/player_properties.spec.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright 2022 Design Barn Inc.
3 | */
4 |
5 | context("Player properties", () => {
6 | beforeEach(() => {
7 | cy.visit("http://localhost:8080/properties");
8 | });
9 |
10 | it("Player-one Should have a green background.", () => {
11 | cy.get("#player-one")
12 | .should("have.css", "background-color")
13 | .and("eq", "rgb(0, 255, 107)");
14 | });
15 |
16 | it("Player-two should play at x5 the speed", () => {
17 | cy.wait(3000);
18 | cy.window().then( (win) => {
19 | if (!win.lottie.getRegisteredAnimations()[1].playSpeed === 5)
20 | done();
21 | });
22 | });
23 |
24 | it("Player-three should autoplay and loop", function (done) {
25 | cy.wait(3000);
26 | cy.window().then( (win) => {
27 | if (!win.lottie.getRegisteredAnimations()[2].isPaused
28 | && win.lottie.getRegisteredAnimations()[2].loop)
29 | done();
30 | });
31 |
32 | });
33 |
34 | it("Player-four should play on hover", function (done) {
35 | cy.wait(3000);
36 | cy.get("#player-four").trigger('mouseenter');
37 | cy.window().then( (win) => {
38 | if (!win.lottie.getRegisteredAnimations()[3].isPaused)
39 | done();
40 | });
41 | });
42 | });
43 |
--------------------------------------------------------------------------------
/cypress/plugins/index.js:
--------------------------------------------------------------------------------
1 | ///
2 | // ***********************************************************
3 | // This example plugins/index.js can be used to load plugins
4 | //
5 | // You can change the location of this file or turn off loading
6 | // the plugins file with the 'pluginsFile' configuration option.
7 | //
8 | // You can read more here:
9 | // https://on.cypress.io/plugins-guide
10 | // ***********************************************************
11 |
12 | // This function is called when a project is opened or re-opened (e.g. due to
13 | // the project's config changing)
14 |
15 | /**
16 | * @type {Cypress.PluginConfig}
17 | */
18 | // eslint-disable-next-line no-unused-vars
19 | module.exports = (on, config) => {
20 | // `on` is used to hook into various events Cypress emits
21 | // `config` is the resolved Cypress config
22 | require('@cypress/code-coverage/task')(on, config)
23 | // include any other plugin code...
24 |
25 | // It's IMPORTANT to return the config object
26 | // with any changed environment variables
27 | return config
28 | }
29 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/.eslintignore:
--------------------------------------------------------------------------------
1 | ./src/components/lottie-react.js
--------------------------------------------------------------------------------
/cypress/react-testing-pages/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/README.md:
--------------------------------------------------------------------------------
1 | # Getting Started with Create React App
2 |
3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
4 |
5 | ## Available Scripts
6 |
7 | In the project directory, you can run:
8 |
9 | ### `npm start`
10 |
11 | Runs the app in the development mode.\
12 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser.
13 |
14 | The page will reload when you make changes.\
15 | You may also see any lint errors in the console.
16 |
17 | ### `npm test`
18 |
19 | Launches the test runner in the interactive watch mode.\
20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
21 |
22 | ### `npm run build`
23 |
24 | Builds the app for production to the `build` folder.\
25 | It correctly bundles React in production mode and optimizes the build for the best performance.
26 |
27 | The build is minified and the filenames include the hashes.\
28 | Your app is ready to be deployed!
29 |
30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
31 |
32 | ### `npm run eject`
33 |
34 | **Note: this is a one-way operation. Once you `eject`, you can't go back!**
35 |
36 | 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.
37 |
38 | 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.
39 |
40 | 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.
41 |
42 | ## Learn More
43 |
44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
45 |
46 | To learn React, check out the [React documentation](https://reactjs.org/).
47 |
48 | ### Code Splitting
49 |
50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting)
51 |
52 | ### Analyzing the Bundle Size
53 |
54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size)
55 |
56 | ### Making a Progressive Web App
57 |
58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app)
59 |
60 | ### Advanced Configuration
61 |
62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration)
63 |
64 | ### Deployment
65 |
66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment)
67 |
68 | ### `npm run build` fails to minify
69 |
70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify)
71 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/craco.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path')
2 | const CracoAlias = require("craco-alias");
3 |
4 | module.exports = {
5 | plugins: [
6 | {
7 | plugin: CracoAlias,
8 | options: {
9 | unsafeAllowModulesOutsideOfSrc : true,
10 | aliases: {
11 | '@lottiefiles/react-lottie-player': path.resolve('../../dist/lottie-react.esm.js'),
12 | }
13 | }
14 | }
15 | ]
16 | };
17 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-testing-pages",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@craco/craco": "^6.4.3",
7 | "@testing-library/jest-dom": "^5.16.2",
8 | "@testing-library/react": "^12.1.3",
9 | "@testing-library/user-event": "^13.5.0",
10 | "react": "^18.1.0",
11 | "react-dom": "^18.1.0",
12 | "react-router": "^6.2.2",
13 | "react-router-dom": "^6.2.2",
14 | "react-scripts": "5.0.1",
15 | "web-vitals": "^2.1.4"
16 | },
17 | "scripts": {
18 | "start": "PORT=8080 craco start",
19 | "build": "craco build",
20 | "test": "craco test",
21 | "eject": "react-scripts eject"
22 | },
23 | "eslintConfig": {
24 | "extends": [
25 | "react-app",
26 | "react-app/jest"
27 | ]
28 | },
29 | "browserslist": {
30 | "production": [
31 | ">0.2%",
32 | "not dead",
33 | "not op_mini all"
34 | ],
35 | "development": [
36 | "last 1 chrome version",
37 | "last 1 firefox version",
38 | "last 1 safari version"
39 | ]
40 | },
41 | "devDependencies": {
42 | "craco-alias": "^3.0.1"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/App.js:
--------------------------------------------------------------------------------
1 | import LoadPage from './components/Load';
2 | import PlayerControls from './components/Controls';
3 | import Properties from './components/Properties';
4 | import Methods from './components/Methods';
5 | import Home from './components/Home';
6 | import { BrowserRouter, Route, Routes } from 'react-router-dom';
7 | import './App.css';
8 |
9 | function App() {
10 | return (
11 |
12 |
13 |
14 | } />
15 | } />
16 | } />
17 | } />
18 | } />
19 |
20 |
21 |
22 | );
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/App.test.js:
--------------------------------------------------------------------------------
1 | import { render, screen } from '@testing-library/react';
2 | import App from './App';
3 |
4 | test('renders learn react link', () => {
5 | render();
6 | const linkElement = screen.getByText(/learn react/i);
7 | expect(linkElement).toBeInTheDocument();
8 | });
9 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/App.css:
--------------------------------------------------------------------------------
1 | .App {
2 | text-align: center;
3 | }
4 |
5 | .App-logo {
6 | height: 40vmin;
7 | pointer-events: none;
8 | }
9 |
10 | @media (prefers-reduced-motion: no-preference) {
11 | .App-logo {
12 | animation: App-logo-spin infinite 20s linear;
13 | }
14 | }
15 |
16 | .App-header {
17 | background-color: #282c34;
18 | min-height: 100vh;
19 | display: flex;
20 | flex-direction: column;
21 | align-items: center;
22 | justify-content: center;
23 | font-size: calc(10px + 2vmin);
24 | color: white;
25 | }
26 |
27 | .App-link {
28 | color: #61dafb;
29 | }
30 |
31 | @keyframes App-logo-spin {
32 | from {
33 | transform: rotate(0deg);
34 | }
35 | to {
36 | transform: rotate(360deg);
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/Controls.js:
--------------------------------------------------------------------------------
1 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
2 | import './App.css';
3 |
4 | function PlayerControls() {
5 | return (
6 |
7 |
17 |
27 |
28 |
38 |
39 |
48 |
49 | {/*
*/}
59 |
60 | );
61 | }
62 |
63 | export default PlayerControls;
64 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/Home.js:
--------------------------------------------------------------------------------
1 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
2 | import './App.css';
3 |
4 | function Home() {
5 | return (
6 |
7 |
14 |
15 |
16 |
22 |
23 |
24 |
30 |
31 |
32 |
36 |
37 |
38 |
39 | );
40 | }
41 |
42 | export default Home;
43 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/Load.js:
--------------------------------------------------------------------------------
1 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
2 | import './App.css';
3 |
4 | function Load() {
5 | return (
6 |
7 |
17 |
18 |
27 |
28 |
37 |
38 |
45 |
46 | );
47 | }
48 |
49 | export default Load;
50 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/Methods.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
3 | import './App.css';
4 |
5 | class Methods extends React.Component {
6 | constructor(props) {
7 | super(props);
8 |
9 | this.pOneRef = React.createRef(null);
10 | this.pTwoRef = React.createRef(null);
11 | this.pThreeRef = React.createRef(null);
12 | this.pFourRef = React.createRef(null);
13 | this.pFiveRef = React.createRef(null);
14 | }
15 |
16 | playFirstAnimation() {
17 | this.pOneRef.current.play();
18 | // Method doesn't exist
19 | // this.pTwoRef.current.loop();
20 | }
21 |
22 | speedUpAnimation() {
23 | this.pThreeRef.current.setPlayerSpeed(5);
24 | }
25 |
26 | pauseAnimation() {
27 | this.pFiveRef.current.pause();
28 | }
29 |
30 | render() {
31 | return (
32 |
33 |
34 |
{
37 | if (event === 'load') this.playFirstAnimation();
38 | }}
39 | id="player-one"
40 | loop
41 | src={process.env.PUBLIC_URL + '/animation.lottie.json'}
42 | style={{ height: '300px', width: '300px' }}>
43 |
44 |
45 |
46 |
47 |
58 |
59 |
60 |
{
63 | if (event === 'load') this.speedUpAnimation();
64 | }}
65 | autoplay
66 | id="player-three"
67 | loop
68 | src={process.env.PUBLIC_URL + '/animation.lottie.json'}
69 | style={{ height: '300px', width: '300px' }}>
70 |
71 |
72 |
73 |
74 |
85 |
86 |
87 |
{
90 | if (event === 'load') this.pauseAnimation();
91 | }}
92 | autoplay
93 | id="player-five"
94 | loop
95 | src={process.env.PUBLIC_URL + '/animation.lottie.json'}
96 | style={{ height: '300px', width: '300px' }}>
97 |
98 |
99 |
100 |
101 | );
102 | }
103 | }
104 |
105 | export default Methods;
106 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/components/Properties.js:
--------------------------------------------------------------------------------
1 | import { Player, Controls } from '@lottiefiles/react-lottie-player';
2 | import './App.css';
3 |
4 | function Properties() {
5 | return (
6 |
7 |
14 |
15 |
16 |
23 |
24 |
25 |
31 |
32 |
33 |
39 |
40 |
41 |
42 | );
43 | }
44 |
45 | export default Properties;
46 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
10 | code {
11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
12 | monospace;
13 | }
14 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { createRoot } from 'react-dom/client';
3 | import './index.css';
4 | import App from './App';
5 | import reportWebVitals from './reportWebVitals';
6 |
7 | const root = createRoot(document.getElementById('root'));
8 |
9 | root.render(
10 |
11 |
12 |
13 | )
14 |
15 |
16 | // If you want to start measuring performance in your app, pass a function
17 | // to log results (for example: reportWebVitals(console.log))
18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
19 | reportWebVitals();
20 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/logo.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/cypress/react-testing-pages/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (config, ...rest) => {
2 | return {
3 | ...config,
4 | resolve: {
5 | modules: [
6 | path.resolve('../../dist/lottie-react.js'),
7 | {
8 | ...config.resolve,
9 | symlinks: false
10 | }
11 | ],
12 | }
13 | }
14 | };
--------------------------------------------------------------------------------
/cypress/support/commands.js:
--------------------------------------------------------------------------------
1 | // ***********************************************
2 | // This example commands.js shows you how to
3 | // create various custom commands and overwrite
4 | // existing commands.
5 | //
6 | // For more comprehensive examples of custom
7 | // commands please read more here:
8 | // https://on.cypress.io/custom-commands
9 | // ***********************************************
10 | //
11 | //
12 | // -- This is a parent command --
13 | // Cypress.Commands.add('login', (email, password) => { ... })
14 | //
15 | //
16 | // -- This is a child command --
17 | // Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
18 | //
19 | //
20 | // -- This is a dual command --
21 | // Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
22 | //
23 | //
24 | // -- This will overwrite an existing command --
25 | // Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
26 |
--------------------------------------------------------------------------------
/cypress/support/index.js:
--------------------------------------------------------------------------------
1 | // ***********************************************************
2 | // This example support/index.js is processed and
3 | // loaded automatically before your test files.
4 | //
5 | // This is a great place to put global configuration and
6 | // behavior that modifies Cypress.
7 | //
8 | // You can change the location of this file or turn off
9 | // automatically serving support files with the
10 | // 'supportFile' configuration option.
11 | //
12 | // You can read more here:
13 | // https://on.cypress.io/configuration
14 | // ***********************************************************
15 |
16 | // Import commands.js using ES2015 syntax:
17 | import './commands'
18 | import '@cypress/code-coverage/support'
19 |
20 | // Alternatively you can use CommonJS syntax:
21 | // require('./commands')
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@lottiefiles/react-lottie-player",
3 | "version": "3.6.0",
4 | "description": "Lottie web player wrapper for React",
5 | "main": "dist/lottie-react.js",
6 | "module": "dist/lottie-react.esm.js",
7 | "types": "dist/src/index.d.ts",
8 | "files": [
9 | "dist",
10 | "src"
11 | ],
12 | "devDependencies": {
13 | "@babel/core": "^7.9.6",
14 | "@babel/preset-env": "^7.16.11",
15 | "@changesets/cli": "^2.21.1",
16 | "@commitlint/cli": "^12.1.1",
17 | "@commitlint/config-conventional": "^12.1.1",
18 | "@cypress/code-coverage": "^3.9.12",
19 | "@istanbuljs/nyc-config-typescript": "^1.0.2",
20 | "@rollup/plugin-babel": "^5.3.1",
21 | "@rollup/plugin-commonjs": "^11.1.0",
22 | "@rollup/plugin-node-resolve": "^7.1.3",
23 | "@rollup/plugin-strip": "^1.3.2",
24 | "@rollup/plugin-typescript": "^8.1.0",
25 | "@semantic-release/changelog": "^5.0.1",
26 | "@semantic-release/commit-analyzer": "^8.0.1",
27 | "@semantic-release/git": "^9.0.0",
28 | "@semantic-release/github": "^7.2.1",
29 | "@semantic-release/npm": "^7.1.1",
30 | "@semantic-release/release-notes-generator": "^9.0.2",
31 | "@storybook/addon-actions": "^5.3.18",
32 | "@storybook/addon-console": "^1.2.2",
33 | "@storybook/addon-links": "^5.3.18",
34 | "@storybook/addons": "^5.3.18",
35 | "@storybook/preset-create-react-app": "^3.0.0",
36 | "@storybook/preset-typescript": "^3.0.0",
37 | "@storybook/react": "^5.3.18",
38 | "@types/jest": "^25.2.3",
39 | "@types/react": "^18.0.9",
40 | "@types/styled-components": "^5.1.0",
41 | "@typescript-eslint/eslint-plugin": "^2.24.0",
42 | "@typescript-eslint/parser": "^2.24.0",
43 | "babel-loader": "^8.1.0",
44 | "babel-plugin-istanbul": "^6.1.1",
45 | "cross-env": "^7.0.3",
46 | "css-loader": "^3.5.3",
47 | "csstype": "^2.6.10",
48 | "cypress": "^9.5.1",
49 | "eslint": "^6.8.0",
50 | "eslint-config-prettier": "^6.10.0",
51 | "eslint-loader": "^3.0.3",
52 | "eslint-plugin-jsx-a11y": "^6.2.3",
53 | "eslint-plugin-prettier": "^3.1.2",
54 | "eslint-plugin-react": "^7.19.0",
55 | "eslint-plugin-react-hooks": "^3.0.0",
56 | "eslint-plugin-simple-import-sort": "^5.0.2",
57 | "husky": ">=4",
58 | "jest": "^26.0.1",
59 | "lint-staged": ">=10",
60 | "prettier": "^1.19.1",
61 | "react": "^19.0.0",
62 | "react-docgen-typescript-loader": "^3.7.2",
63 | "react-dom": "^19.0.0",
64 | "react-scripts": "^3.4.1",
65 | "rollup": "^2.9.1",
66 | "rollup-plugin-filesize": "^9.0.0",
67 | "rollup-plugin-peer-deps-external": "^2.2.2",
68 | "rollup-plugin-postcss": "^3.1.1",
69 | "rollup-plugin-terser": "^5.3.0",
70 | "rollup-plugin-typescript2": "^0.27.0",
71 | "semantic-release": "^17.4.2",
72 | "source-map-support": "^0.5.21",
73 | "style-loader": "^1.2.1",
74 | "ts-jest": "^26.0.0",
75 | "ts-loader": "^7.0.4",
76 | "ts-node": "^10.7.0",
77 | "typescript": "^3.5.3",
78 | "typings-for-css-modules-loader": "^1.7.0",
79 | "webpack": "^4.43.0"
80 | },
81 | "peerDependencies": {
82 | "react": "16 - 19"
83 | },
84 | "dependencies": {
85 | "lottie-web": "^5.12.2"
86 | },
87 | "scripts": {
88 | "predev": "node -p \"'export const REACT_LOTTIE_PLAYER_VERSION = ' + JSON.stringify(require('./package.json').version) + '; \\n' + 'export const LOTTIE_WEB_VERSION = ' + JSON.stringify(require('./package.json').dependencies['lottie-web']) + ';'\" > src/versions.ts",
89 | "dev": "rollup -c -w",
90 | "prebuild": "node -p \"'export const REACT_LOTTIE_PLAYER_VERSION = ' + JSON.stringify(require('./package.json').version) + '; \\n' + 'export const LOTTIE_WEB_VERSION = ' + JSON.stringify(require('./package.json').dependencies['lottie-web']) + ';'\" > src/versions.ts",
91 | "build": "rollup -c",
92 | "prepublishOnly": "yarn build",
93 | "run-tests": "while ! nc -z localhost 8080 ; do sleep 1 ; done && yarn run start-cypress && npx nyc report --reporter=text-summary",
94 | "postrun-tests": "kill $(lsof -t -i:8080)",
95 | "start-and-run-tests": "cross-env NODE_ENV=test yarn run build && yarn run serve-app && yarn run-tests",
96 | "serve-app": "cd ./cypress/react-testing-pages && yarn && yarn run start &",
97 | "start-cypress": "yarn run cypress run",
98 | "lint": "tsc --noEmit && eslint . --ext .ts,.tsx,.js",
99 | "lint:fix": "eslint . --ext .ts,.tsx,.js --fix",
100 | "storybook": "start-storybook -p 9009",
101 | "build-storybook": "build-storybook",
102 | "version": "yarn changeset version"
103 | },
104 | "repository": {
105 | "type": "git",
106 | "url": "git+ssh://git@github.com/LottieFiles/lottie-react.git"
107 | },
108 | "keywords": [
109 | "lottie",
110 | "lottiefiles",
111 | "react"
112 | ],
113 | "author": "Jawish Hameed ",
114 | "contributors": [
115 | "Karam Ali "
116 | ],
117 | "license": "MIT",
118 | "bugs": {
119 | "url": "https://github.com/LottieFiles/lottie-react/issues"
120 | },
121 | "homepage": "https://github.com/LottieFiles/lottie-react#readme",
122 | "husky": {
123 | "hooks": {
124 | "commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
125 | "pre-commit": "lint-staged"
126 | }
127 | },
128 | "lint-staged": {
129 | "src/**/*.{css,scss,md}": [
130 | "prettier --write"
131 | ],
132 | "src/**/*.{js,jsx,ts,tsx,json}": [
133 | "eslint . --ext .ts,.tsx,.js --fix"
134 | ]
135 | },
136 | "nyc": {
137 | "extends": "@istanbuljs/nyc-config-typescript",
138 | "all": true
139 | }
140 | }
141 |
--------------------------------------------------------------------------------
/release.config.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 Design Barn Inc.
3 | */
4 |
5 | 'use strict';
6 |
7 | const pkg = require('./package.json');
8 |
9 | const CHANGELOG_HEADER = `# Changelog
10 | All notable changes to this project will be documented in this file.
11 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), enforced with [semantic-release](https://github.com/semantic-release/semantic-release).
12 | `;
13 |
14 | const SUCCESS_COMMENT = `:tada: This \${issue.pull_request ? 'pull request is included' : 'issue is fixed'} in v\${nextRelease.version}, available on npm: [${pkg.name}@\${nextRelease.version}](https://www.npmjs.com/package/${pkg.name}).`;
15 |
16 | /**
17 | * See:
18 | * https://semantic-release.gitbook.io/semantic-release/
19 | * https://github.com/semantic-release/npm
20 | * https://github.com/semantic-release/github
21 | * https://github.com/semantic-release/git
22 | * https://github.com/semantic-release/release-notes-generator
23 | * https://github.com/semantic-release/commit-analyzer
24 | * https://github.com/semantic-release/changelog
25 | */
26 | module.exports = {
27 | branches: ['master', { name: 'beta', prerelease: true }],
28 | tagFormat: 'v${version}',
29 | ci: true,
30 | plugins: [
31 | [
32 | '@semantic-release/commit-analyzer',
33 | {
34 | preset: 'conventionalcommits',
35 | },
36 | ],
37 | '@semantic-release/release-notes-generator',
38 | [
39 | '@semantic-release/changelog',
40 | {
41 | changelogFile: 'CHANGELOG.md',
42 | changelogTitle: CHANGELOG_HEADER,
43 | },
44 | ],
45 | '@semantic-release/git',
46 | [
47 | '@semantic-release/github',
48 | {
49 | message: 'chore(release): v${nextRelease.version} [skip ci]\n\n${nextRelease.notes}',
50 | assets: 'dist/*.tgz',
51 | successComment: SUCCESS_COMMENT,
52 | addReleases: 'bottom',
53 | },
54 | ],
55 | [
56 | '@semantic-release/npm',
57 | {
58 | npmPublish: true,
59 | tarballDir: 'dist',
60 | },
61 | ],
62 | ],
63 | };
64 |
--------------------------------------------------------------------------------
/rollup.config.js:
--------------------------------------------------------------------------------
1 | import babel from '@rollup/plugin-babel';
2 | import commonjs from '@rollup/plugin-commonjs';
3 | import resolve from '@rollup/plugin-node-resolve';
4 | import strip from '@rollup/plugin-strip';
5 | import typescript from '@rollup/plugin-typescript';
6 | import filesize from 'rollup-plugin-filesize';
7 | import peerDepsExternal from 'rollup-plugin-peer-deps-external';
8 | import postcss from 'rollup-plugin-postcss';
9 | import { terser } from 'rollup-plugin-terser';
10 |
11 | const pkg = require('./package.json');
12 |
13 | const isProduction = !process.env.ROLLUP_WATCH;
14 | const name = 'ReactLottiePlayer';
15 |
16 | export default {
17 | input: './src/index.ts',
18 | treeshake: false,
19 | output: [
20 | {
21 | file: pkg.main,
22 | format: 'cjs',
23 | sourcemap: true,
24 | name,
25 | },
26 | {
27 | file: pkg.module,
28 | format: 'es',
29 | sourcemap: true,
30 | name,
31 | },
32 | ],
33 | plugins: [
34 | // Remove debugger statements and console.log calls
35 | isProduction && strip(),
36 |
37 | // Externalize peerDependencies
38 | peerDepsExternal(),
39 |
40 | // Resolve packages from node_modules
41 | resolve({ jsnext: true, extensions: ['.ts', '.js', '.tsx'] }),
42 |
43 | // Convert commonjs modules to ES modules
44 | commonjs(),
45 |
46 | babel({
47 | extensions: ['.js', '.jsx', '.ts', '.tsx', '.mjs'],
48 | exclude: ['./node_modules/**'],
49 | }),
50 |
51 | // Use Typescript to transpile code
52 | // typescript({ lib: ['es5', 'es6', 'dom'], target: 'es5' }),
53 | typescript({ tsconfig: './tsconfig.json' }),
54 |
55 | // In production mode, minify
56 | isProduction && terser(),
57 |
58 | // Show output filesize
59 | filesize(),
60 |
61 | // bundle with css files
62 | postcss({
63 | extensions: ['.css'],
64 | }),
65 | ],
66 | };
67 |
--------------------------------------------------------------------------------
/src/ColorPicker.tsx:
--------------------------------------------------------------------------------
1 | import * as React from 'react';
2 |
3 | interface ColorPickerProps {
4 | colorChangedEvent?: (hex: string) => void;
5 | }
6 | export class ColorPicker extends React.Component {
7 | state = {
8 | red: 0,
9 | green: 0,
10 | blue: 0,
11 | rgba: null,
12 | hex: '#000000',
13 | colorComponents: [],
14 | };
15 |
16 | handleChange = (rgb: string, value: any) => {
17 | if (rgb === 'r') {
18 | const hex =
19 | '#' +
20 | (value | (1 << 8)).toString(16).slice(1) +
21 | (this.state.green | (1 << 8)).toString(16).slice(1) +
22 | (this.state.blue | (1 << 8)).toString(16).slice(1);
23 | this.setState({ hex: hex });
24 | } else if (rgb === 'g') {
25 | const hex =
26 | '#' +
27 | (this.state.red | (1 << 8)).toString(16).slice(1) +
28 | (value | (1 << 8)).toString(16).slice(1) +
29 | (this.state.blue | (1 << 8)).toString(16).slice(1);
30 | this.setState({ hex: hex });
31 | } else if (rgb === 'b') {
32 | const hex =
33 | '#' +
34 | (this.state.red | (1 << 8)).toString(16).slice(1) +
35 | (this.state.green | (1 << 8)).toString(16).slice(1) +
36 | (value | (1 << 8)).toString(16).slice(1);
37 | this.setState({ hex: hex });
38 | }
39 | };
40 | parseColor = (input: string) => {
41 | if (typeof input !== 'string') {
42 | return;
43 | }
44 | if (input[0] === '#') {
45 | const _colorComponents =
46 | input.length === 4
47 | ? [input.slice(1, 2), input.slice(2, 3), input.slice(3, 4)].map(n => parseInt(`${n}${n}`, 16))
48 | : [input.slice(1, 3), input.slice(3, 5), input.slice(5, 7)].map(n => parseInt(n, 16));
49 | this.setState({ colorComponents: _colorComponents });
50 | } else if (input.startsWith('rgb')) {
51 | const _colorComponents = input.match(/\d+/g)?.map(n => parseInt(n));
52 | if (_colorComponents !== undefined) {
53 | this.setState({ colorComponents: _colorComponents });
54 | }
55 | }
56 |
57 | if (this.state.colorComponents.length) {
58 | this.setState({ red: this.state.colorComponents[0] });
59 | this.setState({ green: this.state.colorComponents[1] });
60 | this.setState({ blue: this.state.colorComponents[2] });
61 | }
62 | };
63 |
64 | componentDidUpdate(_prevProps: any, prevState: any) {
65 | if (this.props.colorChangedEvent) {
66 | if (this.state.hex !== prevState.hex) {
67 | this.props.colorChangedEvent(this.state.hex);
68 | }
69 | }
70 | return true;
71 | }
72 | public render() {
73 | return (
74 |
75 |
149 |
150 |
154 |
155 | {
160 | this.setState({ hex: e.target.value });
161 | this.parseColor(e.target.value);
162 | }}
163 | />
164 |
165 |
166 |
167 | );
168 | }
169 | }
170 |
--------------------------------------------------------------------------------
/src/Controls.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/no-static-element-interactions */
2 | /* eslint-disable jsx-a11y/click-events-have-key-events */
3 | import './Styles.css';
4 |
5 | import * as React from 'react';
6 |
7 | import { ColorPicker } from './ColorPicker';
8 | import { PlayerEvent, PlayerState } from './Player';
9 | import { Popover } from './Popover';
10 | import { Seeker } from './Seeker';
11 |
12 | const ControlButtonStyle = {
13 | display: 'inline-flex',
14 | cursor: 'pointer',
15 | };
16 |
17 | interface IControlProps {
18 | instance?: any;
19 | loop?: boolean;
20 | pause?: () => void;
21 | play?: () => void;
22 | playerState?: PlayerState;
23 | seeker?: number;
24 | setLoop?: (value: boolean) => void;
25 | setSeeker?: (seek: number, play: boolean) => void;
26 | stop?: () => void;
27 | visible?: boolean;
28 | buttons?: string[];
29 | debug?: boolean;
30 | toggleDebug?: () => void;
31 | showLabels?: boolean;
32 | darkTheme?: boolean;
33 | transparentTheme?: boolean;
34 | colorChangedEvent?: () => void;
35 | snapshot?: () => void;
36 | }
37 |
38 | export class Controls extends React.Component {
39 | public constructor(props: IControlProps) {
40 | super(props);
41 |
42 | this.state = {
43 | activeFrame: 0,
44 | mouseDown: false,
45 | };
46 | }
47 |
48 | public render() {
49 | const { instance, playerState, seeker, setLoop, setSeeker, play, pause, stop, visible, buttons } = this.props;
50 |
51 | // Render nothing if lottie instance is not available
52 | if (!instance) {
53 | return null;
54 | }
55 |
56 | // Hide controls if not set to visible
57 | if (!visible) {
58 | return null;
59 | }
60 |
61 | const showPlayButton = !buttons || buttons.includes('play');
62 | const showStopButton = !buttons || buttons.includes('stop');
63 | const showRepeatButton = !buttons || buttons.includes('repeat');
64 | const showFrameInput = !buttons || buttons.includes('frame');
65 | const showBackgroundChange = !buttons || buttons.includes('background');
66 | const showSnapshot = !buttons || buttons.includes('snapshot');
67 | const ICON_SIZE = { width: 14, height: 14, viewBox: '0 0 24 24' };
68 | const currentFrame = Math.round(instance.currentFrame);
69 |
70 | return (
71 |
83 | {showPlayButton && (
84 |
{
89 | if (playerState === PlayerState.Playing) {
90 | if (typeof pause === 'function') {
91 | pause();
92 | }
93 | } else {
94 | if (typeof play === 'function') {
95 | play();
96 | }
97 | }
98 | }}
99 | onKeyDown={() => {
100 | if (playerState === PlayerState.Playing) {
101 | if (typeof pause === 'function') {
102 | pause();
103 | }
104 | } else {
105 | if (typeof play === 'function') {
106 | play();
107 | }
108 | }
109 | }}
110 | className="lf-player-btn"
111 | style={ControlButtonStyle}
112 | >
113 | {playerState === PlayerState.Playing ? (
114 |
118 | ) : (
119 |
122 | )}
123 |
124 | )}
125 | {showStopButton && (
126 |
stop && stop()}
131 | onKeyDown={() => stop && stop()}
132 | className={playerState === PlayerState.Stopped ? 'lf-player-btn active' : 'lf-player-btn'}
133 | style={ControlButtonStyle}
134 | >
135 |
141 |
142 | )}
143 |
{
149 | if (setSeeker) {
150 | this.setState({ activeFrame: newFrame }, () => {
151 | setSeeker(newFrame, false);
152 | });
153 | }
154 | }}
155 | onChangeEnd={(newFrame: any) => {
156 | if (setSeeker) {
157 | this.setState({ activeFrame: newFrame }, () => {
158 | setSeeker(newFrame, false);
159 | });
160 | }
161 | }}
162 | showLabels={this.props.showLabels}
163 | darkTheme={this.props.darkTheme}
164 | />
165 | {showFrameInput && (
166 |
167 |
184 |
185 | )}
186 | {showRepeatButton && (
187 | {
192 | if (instance && setLoop) {
193 | setLoop(!instance.loop);
194 | }
195 | }}
196 | onKeyDown={() => {
197 | if (instance && setLoop) {
198 | setLoop(!instance.loop);
199 | }
200 | }}
201 | className={instance.loop ? 'lf-player-btn active' : 'lf-player-btn'}
202 | style={ControlButtonStyle}
203 | >
204 |
230 |
231 | )}
232 | {showBackgroundChange && (
233 |
236 |
243 |
244 | }
245 | >
246 |
247 |
248 |
249 |
250 | )}
251 | {showSnapshot && (
252 |
255 |
262 |
263 |
264 |
270 |
271 | }
272 | >
273 | {
277 | if (setSeeker) setSeeker(currentFrame + (e.deltaY > 0 ? -1 : 1), false);
278 | }}
279 | >
280 |
Frame {currentFrame}
281 |
282 | Download SVG
283 |
284 |
285 | Download PNG
286 |
287 |
Scroll with mousewheel to find exact frame
288 |
289 |
290 | )}
291 |
292 | );
293 | }
294 | }
295 |
--------------------------------------------------------------------------------
/src/Player.tsx:
--------------------------------------------------------------------------------
1 | import lottie, { AnimationItem } from 'lottie-web';
2 | import * as React from 'react';
3 |
4 | import { LOTTIE_WEB_VERSION, REACT_LOTTIE_PLAYER_VERSION } from './versions';
5 |
6 | /**
7 | * Parse a resource into a JSON object or a URL string
8 | */
9 | export function parseSrc(src: string | object): string | object {
10 | if (typeof src === 'object') {
11 | return src;
12 | }
13 |
14 | try {
15 | return JSON.parse(src);
16 | } catch (e) {
17 | // Do nothing...
18 | }
19 |
20 | // Try construct an absolute URL from the src URL
21 | try {
22 | return new URL(src).toString();
23 | } catch (e) {
24 | // Do nothing...
25 | }
26 |
27 | return src;
28 | }
29 |
30 | // Necessary so that we can add Lottie to the window afterwards
31 | declare global {
32 | interface Window {
33 | lottie: any;
34 | }
35 | }
36 |
37 | // Define valid player states
38 | export enum PlayerState {
39 | Loading = 'loading',
40 | Playing = 'playing',
41 | Paused = 'paused',
42 | Stopped = 'stopped',
43 | Frozen = 'frozen',
44 | Error = 'error',
45 | }
46 |
47 | // Define player events
48 | export enum PlayerEvent {
49 | Load = 'load',
50 | InstanceSaved = 'instanceSaved',
51 | Error = 'error',
52 | Ready = 'ready',
53 | Play = 'play',
54 | Pause = 'pause',
55 | Stop = 'stop',
56 | Freeze = 'freeze',
57 | Loop = 'loop',
58 | Complete = 'complete',
59 | Frame = 'frame',
60 | }
61 |
62 | export type Versions = {
63 | lottieWebVersion: string;
64 | lottiePlayerVersion: string;
65 | };
66 |
67 | export type PlayerDirection = -1 | 1;
68 |
69 | export interface IPlayerProps {
70 | id?: string;
71 | lottieRef?: (ref: AnimationItem) => void;
72 | onEvent?: (event: PlayerEvent) => any;
73 | onStateChange?: (state: PlayerState) => any;
74 | onBackgroundChange?: (color: string) => void;
75 | autoplay?: boolean;
76 | background?: string;
77 | children?: React.ReactNode | React.ReactNode[];
78 | controls?: boolean;
79 | direction?: PlayerDirection;
80 | hover?: boolean;
81 | loop?: boolean | number;
82 | renderer?: any;
83 | speed?: number;
84 | src: object | string;
85 | style?: React.CSSProperties;
86 | rendererSettings?: object;
87 | keepLastFrame?: boolean;
88 | className?: string;
89 | }
90 |
91 | interface IPlayerState {
92 | animationData: any;
93 | background: string;
94 | containerRef: React.Ref | null;
95 | debug?: boolean;
96 | instance: AnimationItem | null;
97 | seeker: number;
98 | playerState: PlayerState;
99 | }
100 |
101 | // Build default config for lottie-web player
102 | const defaultOptions = {
103 | clearCanvas: false,
104 | hideOnTransparent: true,
105 | progressiveLoad: true,
106 | };
107 |
108 | export class Player extends React.Component {
109 | public static async getDerivedStateFromProps(nextProps: any, prevState: any) {
110 | if (nextProps.background !== prevState.background) {
111 | return { background: nextProps.background };
112 | } else {
113 | return null;
114 | }
115 | }
116 |
117 | public container: Element | null = null;
118 | public unmounted = false;
119 |
120 | constructor(props: IPlayerProps) {
121 | super(props);
122 |
123 | if (typeof window !== 'undefined') {
124 | window.lottie = lottie;
125 | }
126 | this.state = {
127 | animationData: null,
128 | background: 'transparent',
129 | containerRef: React.createRef(),
130 | debug: true,
131 | instance: null,
132 | playerState: PlayerState.Loading,
133 | seeker: 0,
134 | };
135 | }
136 |
137 | /**
138 | * Returns the lottie-web version and this player's version
139 | */
140 | public getVersions(): Versions {
141 | return {
142 | lottieWebVersion: LOTTIE_WEB_VERSION,
143 | lottiePlayerVersion: REACT_LOTTIE_PLAYER_VERSION,
144 | };
145 | }
146 |
147 | static defaultProps = {
148 | loop: false,
149 | };
150 |
151 | public async componentDidMount() {
152 | if (!this.unmounted) {
153 | await this.createLottie();
154 | }
155 | }
156 |
157 | public componentWillUnmount() {
158 | this.unmounted = true;
159 | if (this.state.instance) {
160 | this.state.instance.destroy();
161 | }
162 | }
163 |
164 | public async componentDidUpdate(prevProps: any) {
165 | if (this.props.src !== prevProps.src) {
166 | if (this.state.instance) {
167 | this.state.instance.destroy();
168 | }
169 | await this.createLottie();
170 | }
171 | }
172 | handleBgChange = (childData: any) => {
173 | this.setState({ background: childData });
174 | };
175 | triggerDownload = (dataUri: any, filename: any) => {
176 | const element = document.createElement('a');
177 |
178 | element.href = dataUri;
179 | element.download = filename;
180 | document.body.appendChild(element);
181 |
182 | element.click();
183 |
184 | document.body.removeChild(element);
185 | };
186 | snapshot = (download = true) => {
187 | let data;
188 | const id = this.props.id ? this.props.id : 'lottie';
189 | const lottieElement = document.getElementById(id);
190 | if (this.props.renderer === 'svg') {
191 | // Get SVG element and serialize markup
192 | if (lottieElement) {
193 | const svgElement = lottieElement.querySelector('svg');
194 |
195 | if (svgElement) {
196 | const serializedSvg = new XMLSerializer().serializeToString(svgElement);
197 | data = 'data:image/svg+xml;charset=utf-8,' + encodeURIComponent(serializedSvg);
198 | }
199 | }
200 |
201 | // Trigger file download if needed
202 | if (download) {
203 | // this.triggerDownload(data, `snapshot_${progress}.svg`);
204 | this.triggerDownload(data, `snapshot.svg`);
205 | }
206 | } else if (this.props.renderer === 'canvas') {
207 | if (lottieElement) {
208 | const canvas = lottieElement.querySelector('canvas');
209 | if (canvas) {
210 | data = canvas.toDataURL('image/png');
211 | }
212 | }
213 | // Trigger file download if needed
214 | if (download) {
215 | // this.triggerDownload(data, `snapshot_${progress}.png`);
216 | this.triggerDownload(data, `snapshot.png`);
217 | }
218 | }
219 |
220 | return data;
221 | };
222 |
223 | public render() {
224 | const { children, loop, style, onBackgroundChange, className } = this.props;
225 | const { animationData, instance, playerState, seeker, debug, background } = this.state;
226 |
227 | return (
228 |
229 | {this.state.playerState === PlayerState.Error ? (
230 |
231 |
232 | ⚠️
233 |
234 |
235 | ) : (
236 |
(this.container = el)}
239 | style={{
240 | background,
241 | margin: '0 auto',
242 | outline: 'none',
243 | overflow: 'hidden',
244 | ...style,
245 | }}
246 | className={className}
247 | >
248 | )}
249 | {React.Children.map(children, child => {
250 | if (React.isValidElement(child)) {
251 | return React.cloneElement(child, {
252 | animationData,
253 | background,
254 | debug,
255 | instance,
256 | loop,
257 | pause: () => this.pause(),
258 | play: () => this.play(),
259 | playerState,
260 | seeker,
261 | setBackground: (value: string) => {
262 | this.setState({ background: value });
263 |
264 | if (typeof onBackgroundChange === 'function') {
265 | onBackgroundChange(value);
266 | }
267 | },
268 | setSeeker: (f: number, p: boolean) => this.setSeeker(f, p),
269 | stop: () => this.stop(),
270 | toggleDebug: () => this.toggleDebug(),
271 | setLoop: (loop: boolean) => this.setLoop(loop),
272 | colorChangedEvent: (hex: string) => {
273 | this.handleBgChange(hex);
274 | },
275 | snapshot: () => {
276 | this.snapshot();
277 | },
278 | });
279 | }
280 | return null;
281 | })}
282 |
283 | );
284 | }
285 |
286 | private toggleDebug() {
287 | this.setState({ debug: !this.state.debug });
288 | }
289 |
290 | private async createLottie() {
291 | const {
292 | autoplay,
293 | direction,
294 | loop,
295 | lottieRef,
296 | renderer,
297 | speed,
298 | src,
299 | background,
300 | rendererSettings,
301 | hover,
302 | } = this.props;
303 | const { instance } = this.state;
304 |
305 | if (!src || !this.container) {
306 | return;
307 | }
308 |
309 | // Load the resource information
310 | try {
311 | // Parse the src to see if it is a URL or Lottie JSON data
312 | let animationData = parseSrc(src);
313 |
314 | if (typeof animationData === 'string') {
315 | const fetchResult = await fetch(animationData as string).catch(() => {
316 | this.setState({ playerState: PlayerState.Error });
317 | this.triggerEvent(PlayerEvent.Error);
318 | throw new Error('@LottieFiles/lottie-react: Animation data could not be fetched.');
319 | });
320 |
321 | animationData = await fetchResult.json().catch(() => {
322 | this.setState({ playerState: PlayerState.Error });
323 | this.triggerEvent(PlayerEvent.Error);
324 | throw new Error('@LottieFiles/lottie-react: Animation data could not be fetched.');
325 | });
326 | }
327 |
328 | // Clear previous animation, if any
329 | if (instance) {
330 | instance.destroy();
331 | }
332 |
333 | // Initialize lottie player and load animation
334 | const newInstance = lottie.loadAnimation({
335 | rendererSettings: rendererSettings || defaultOptions,
336 | animationData,
337 | autoplay: autoplay || false,
338 | container: this.container as Element,
339 | loop: loop || false,
340 | renderer,
341 | });
342 | if (speed) {
343 | newInstance.setSpeed(speed);
344 | }
345 | this.setState({ animationData });
346 |
347 | this.setState({ instance: newInstance }, () => {
348 | this.triggerEvent(PlayerEvent.InstanceSaved);
349 |
350 | if (typeof lottieRef === 'function') {
351 | lottieRef(newInstance);
352 | }
353 | if (autoplay) {
354 | this.play();
355 | }
356 | });
357 |
358 | // Handle new frame event
359 | newInstance.addEventListener('enterFrame', () => {
360 | this.triggerEvent(PlayerEvent.Frame);
361 |
362 | this.setState({
363 | seeker: Math.floor((newInstance as any).currentFrame),
364 | });
365 | });
366 |
367 | // Handle lottie-web ready event
368 | newInstance.addEventListener('DOMLoaded', () => {
369 | this.triggerEvent(PlayerEvent.Load);
370 | });
371 |
372 | // Handle animation data load complete
373 | newInstance.addEventListener('data_ready', () => {
374 | this.triggerEvent(PlayerEvent.Ready);
375 | });
376 |
377 | // Set error state when animation load fail event triggers
378 | newInstance.addEventListener('data_failed', () => {
379 | this.setState({ playerState: PlayerState.Error });
380 | this.triggerEvent(PlayerEvent.Error);
381 | });
382 |
383 | // Handle new loop event
384 | newInstance.addEventListener('loopComplete', () => {
385 | this.triggerEvent(PlayerEvent.Loop);
386 | });
387 |
388 | // Set state to paused if loop is off and anim has completed
389 | newInstance.addEventListener('complete', () => {
390 | this.triggerEvent(PlayerEvent.Complete);
391 | this.setState({ playerState: PlayerState.Paused });
392 |
393 | if (!this.props.keepLastFrame || this.props.loop) {
394 | this.setSeeker(0);
395 | }
396 | });
397 |
398 | // Set handlers to auto play animation on hover if enabled
399 | if (this.container) {
400 | this.container.addEventListener('mouseenter', () => {
401 | if (hover && this.state.playerState !== PlayerState.Playing) {
402 | if (this.props.keepLastFrame) {
403 | this.stop();
404 | }
405 | this.play();
406 | }
407 | });
408 | this.container.addEventListener('mouseleave', () => {
409 | if (hover && this.state.playerState === PlayerState.Playing) {
410 | this.stop();
411 | }
412 | });
413 | }
414 |
415 | // Set initial playback speed and direction
416 | if (speed) {
417 | this.setPlayerSpeed(speed);
418 | }
419 |
420 | if (direction) {
421 | this.setPlayerDirection(direction);
422 | }
423 |
424 | if (background) {
425 | this.setState({ background });
426 | }
427 | } catch (e) {
428 | this.setState({ playerState: PlayerState.Error });
429 | this.triggerEvent(PlayerEvent.Error);
430 | }
431 | }
432 |
433 | public play() {
434 | const { instance } = this.state;
435 |
436 | if (instance) {
437 | this.triggerEvent(PlayerEvent.Play);
438 |
439 | instance.play();
440 |
441 | this.setState({ playerState: PlayerState.Playing });
442 | }
443 | }
444 |
445 | public pause() {
446 | const { instance } = this.state;
447 |
448 | if (instance) {
449 | this.triggerEvent(PlayerEvent.Pause);
450 |
451 | instance.pause();
452 |
453 | this.setState({ playerState: PlayerState.Paused });
454 | }
455 | }
456 |
457 | public stop() {
458 | const { instance } = this.state;
459 |
460 | if (instance) {
461 | this.triggerEvent(PlayerEvent.Stop);
462 |
463 | instance.stop();
464 |
465 | this.setState({ playerState: PlayerState.Stopped });
466 | }
467 | }
468 |
469 | public setPlayerSpeed(speed: number) {
470 | const { instance } = this.state;
471 |
472 | if (instance) {
473 | instance.setSpeed(speed);
474 | }
475 | }
476 |
477 | public setPlayerDirection(direction: PlayerDirection) {
478 | const { instance } = this.state;
479 |
480 | if (instance) {
481 | instance.setDirection(direction);
482 | }
483 | }
484 |
485 | public setSeeker(seek: number, play = false) {
486 | const { instance, playerState } = this.state;
487 |
488 | if (instance) {
489 | if (!play || playerState !== PlayerState.Playing) {
490 | instance.goToAndStop(seek, true);
491 | this.triggerEvent(PlayerEvent.Pause);
492 | this.setState({ playerState: PlayerState.Paused });
493 | } else {
494 | instance.goToAndPlay(seek, true);
495 | }
496 | }
497 | }
498 |
499 | public setLoop(loop: boolean) {
500 | const { instance } = this.state;
501 |
502 | if (instance) {
503 | instance.loop = loop;
504 | this.setState({ instance: instance });
505 | }
506 | }
507 | private triggerEvent(event: PlayerEvent) {
508 | const { onEvent } = this.props;
509 |
510 | if (onEvent) {
511 | onEvent(event);
512 | }
513 | }
514 | }
515 |
--------------------------------------------------------------------------------
/src/Popover.tsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable jsx-a11y/mouse-events-have-key-events */
2 | /* eslint-disable react/jsx-no-comment-textnodes */
3 | import * as React from 'react';
4 | import { useEffect, useState } from 'react';
5 | import { ReactNode } from 'react';
6 |
7 | interface IPopoverProps {
8 | children: ReactNode;
9 | icon: ReactNode;
10 | }
11 |
12 | export const Popover: React.FC = (props: IPopoverProps) => {
13 | const { children, icon } = props;
14 | const [_triggerRef, setTriggerRef] = useState(null);
15 | const [_contentRef, setContentRef] = useState(null);
16 | const [_alignment, setAlignment] = useState(null);
17 | const [_open, setOpen] = useState(false);
18 |
19 | useEffect(() => {
20 | if (_triggerRef && _contentRef) {
21 | const triggerBounds = _triggerRef.getBoundingClientRect();
22 | const contentBounds = _contentRef.getBoundingClientRect();
23 |
24 | const alignment = triggerBounds.left + contentBounds.width > window.innerWidth ? -1 : 0;
25 |
26 | setAlignment(alignment);
27 | }
28 | }, [_alignment, _contentRef, _triggerRef]);
29 |
30 | // /**
31 | // * Show content box
32 | // */
33 | const show = () => {
34 | setOpen(true);
35 | };
36 |
37 | /**
38 | * Hide content box
39 | */
40 | const hide = () => {
41 | setOpen(false);
42 | };
43 |
44 | return (
45 | {
48 | show();
49 | }}
50 | onMouseLeave={() => {
51 | hide();
52 | }}
53 | ref={triggerRef => {
54 | setTriggerRef(triggerRef);
55 | }}
56 | >
57 |
{icon}
58 |
59 |
{
62 | setContentRef(contentRef);
63 | }}
64 | style={{
65 | bottom: '22px',
66 | right: '0px',
67 | zIndex: 2,
68 | visibility: _open ? 'visible' : 'hidden',
69 | }}
70 | >
71 | {children}
72 |
73 |
74 | );
75 | };
76 | export default Popover;
77 |
--------------------------------------------------------------------------------
/src/Seeker.tsx:
--------------------------------------------------------------------------------
1 | import './Styles.css';
2 |
3 | import * as React from 'react';
4 |
5 | // const active = 'rgba(15, 204, 206, 0.4)';
6 | // const inactive = '#dbdbdb';
7 |
8 | interface ISeekerProps {
9 | className?: string;
10 | disabled?: boolean;
11 | max: number;
12 | min: number;
13 | onChange: (e: any) => void;
14 | onChangeStart?: (v: number) => void;
15 | onChangeEnd?: (v: number) => void;
16 | showLabels?: boolean;
17 | step: number;
18 | value: number;
19 | darkTheme?: boolean;
20 | }
21 |
22 | export class Seeker extends React.Component {
23 | inputRef: any = React.createRef();
24 |
25 | constructor(props: ISeekerProps) {
26 | super(props);
27 | this.state = { value: 0 };
28 | }
29 |
30 | handleChange = () => (event: any) => {
31 | const value = event.target.value;
32 | const frame = Math.floor((value / 100) * this.props.max);
33 | this.props.onChange(frame);
34 | };
35 |
36 | render() {
37 | const progress = (this.props.value / this.props.max) * 100;
38 | const seekerStyle = {
39 | backgroundImage: `-webkit-gradient(linear, left top, right top, color-stop(${progress}%, rgba(15, 204, 206, 0.4)), color-stop(${progress}%, #DAE1E7))`,
40 | };
41 | const seekerContainerStyle = {
42 | display: 'flex',
43 | flexDirection: 'column',
44 | alignItems: 'center',
45 | width: '100%',
46 | marginRight: '5px',
47 | marginLeft: '5px',
48 | position: 'relative',
49 | } as React.CSSProperties;
50 |
51 | const minLabelStyle = {
52 | position: 'absolute',
53 | left: 0,
54 | marginTop: '8px',
55 | width: '20px',
56 | display: 'block',
57 | border: '0px',
58 | backgroundColor: this.props.darkTheme ? '#505050' : 'rgb(218, 225, 231)',
59 | color: this.props.darkTheme ? '#B9B9B9' : '#555',
60 | padding: '2px',
61 | textAlign: 'center',
62 | borderRadius: '3px',
63 | fontSize: '8px',
64 | fontWeight: 'bold',
65 | } as React.CSSProperties;
66 | const maxLabelStyle = {
67 | position: 'absolute',
68 | right: 0,
69 | marginTop: '8px',
70 | width: '20px',
71 | display: 'block',
72 | border: '0px',
73 | backgroundColor: this.props.darkTheme ? '#505050' : 'rgb(218, 225, 231)',
74 | color: this.props.darkTheme ? '#B9B9B9' : '#555',
75 | padding: '2px',
76 | textAlign: 'center',
77 | borderRadius: '3px',
78 | fontSize: '8px',
79 | fontWeight: 'bold',
80 | } as React.CSSProperties;
81 | return (
82 |
83 |
98 | {this.props.showLabels && (
99 |
100 |
{this.props.min}
101 |
{this.props.max}
102 |
103 | )}
104 |
105 | );
106 | }
107 | }
108 |
--------------------------------------------------------------------------------
/src/Styles.css:
--------------------------------------------------------------------------------
1 | .lf-progress {
2 | -webkit-appearance: none;
3 | -moz-apperance: none;
4 | width: 100%;
5 | /* margin: 0 10px; */
6 | height: 4px;
7 | border-radius: 3px;
8 | cursor: pointer;
9 | }
10 | .lf-progress:focus {
11 | outline: none;
12 | border: none;
13 | }
14 | .lf-progress::-moz-range-track {
15 | cursor: pointer;
16 | background: none;
17 | border: none;
18 | outline: none;
19 | }
20 | .lf-progress::-webkit-slider-thumb {
21 | -webkit-appearance: none !important;
22 | height: 13px;
23 | width: 13px;
24 | border: 0;
25 | border-radius: 50%;
26 | background: #0fccce;
27 | cursor: pointer;
28 | }
29 | .lf-progress::-moz-range-thumb {
30 | -moz-appearance: none !important;
31 | height: 13px;
32 | width: 13px;
33 | border: 0;
34 | border-radius: 50%;
35 | background: #0fccce;
36 | cursor: pointer;
37 | }
38 | .lf-progress::-ms-track {
39 | width: 100%;
40 | height: 3px;
41 | cursor: pointer;
42 | background: transparent;
43 | border-color: transparent;
44 | color: transparent;
45 | }
46 | .lf-progress::-ms-fill-lower {
47 | background: #ccc;
48 | border-radius: 3px;
49 | }
50 | .lf-progress::-ms-fill-upper {
51 | background: #ccc;
52 | border-radius: 3px;
53 | }
54 | .lf-progress::-ms-thumb {
55 | border: 0;
56 | height: 15px;
57 | width: 15px;
58 | border-radius: 50%;
59 | background: #0fccce;
60 | cursor: pointer;
61 | }
62 | .lf-progress:focus::-ms-fill-lower {
63 | background: #ccc;
64 | }
65 | .lf-progress:focus::-ms-fill-upper {
66 | background: #ccc;
67 | }
68 | .lf-player-container :focus {
69 | outline: 0;
70 | }
71 | .lf-popover {
72 | position: relative;
73 | }
74 |
75 | .lf-popover-content {
76 | display: inline-block;
77 | position: absolute;
78 | opacity: 1;
79 | visibility: visible;
80 | transform: translate(0, -10px);
81 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
82 | transition: all 0.3s cubic-bezier(0.75, -0.02, 0.2, 0.97);
83 | }
84 |
85 | .lf-popover-content.hidden {
86 | opacity: 0;
87 | visibility: hidden;
88 | transform: translate(0, 0px);
89 | }
90 |
91 | .lf-player-btn-container {
92 | display: flex;
93 | align-items: center;
94 | }
95 | .lf-player-btn {
96 | cursor: pointer;
97 | fill: #999;
98 | width: 14px;
99 | }
100 |
101 | .lf-player-btn.active {
102 | fill: #555;
103 | }
104 |
105 | .lf-popover {
106 | position: relative;
107 | }
108 |
109 | .lf-popover-content {
110 | display: inline-block;
111 | position: absolute;
112 | background-color: #ffffff;
113 | opacity: 1;
114 |
115 | transform: translate(0, -10px);
116 | box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.26);
117 | transition: all 0.3s cubic-bezier(0.75, -0.02, 0.2, 0.97);
118 | padding: 10px;
119 | }
120 |
121 | .lf-popover-content.hidden {
122 | opacity: 0;
123 | visibility: hidden;
124 | transform: translate(0, 0px);
125 | }
126 |
127 | .lf-arrow {
128 | position: absolute;
129 | z-index: -1;
130 | content: '';
131 | bottom: -9px;
132 | border-style: solid;
133 | border-width: 10px 10px 0px 10px;
134 | }
135 |
136 | .lf-left-align,
137 | .lf-left-align .lfarrow {
138 | left: 0;
139 | right: unset;
140 | }
141 |
142 | .lf-right-align,
143 | .lf-right-align .lf-arrow {
144 | right: 0;
145 | left: unset;
146 | }
147 |
148 | .lf-text-input {
149 | border: 1px #ccc solid;
150 | border-radius: 5px;
151 | padding: 3px;
152 | width: 60px;
153 | margin: 0;
154 | }
155 |
156 | .lf-color-picker {
157 | display: flex;
158 | flex-direction: row;
159 | justify-content: space-between;
160 | height: 90px;
161 | }
162 |
163 | .lf-color-selectors {
164 | display: flex;
165 | flex-direction: column;
166 | justify-content: space-between;
167 | }
168 |
169 | .lf-color-component {
170 | display: flex;
171 | flex-direction: row;
172 | font-size: 12px;
173 | align-items: center;
174 | justify-content: center;
175 | }
176 |
177 | .lf-color-component strong {
178 | width: 40px;
179 | }
180 |
181 | .lf-color-component input[type='range'] {
182 | margin: 0 0 0 10px;
183 | }
184 |
185 | .lf-color-component input[type='number'] {
186 | width: 50px;
187 | margin: 0 0 0 10px;
188 | }
189 |
190 | .lf-color-preview {
191 | font-size: 12px;
192 | display: flex;
193 | flex-direction: column;
194 | align-items: center;
195 | justify-content: space-between;
196 | padding-left: 5px;
197 | }
198 |
199 | .lf-preview {
200 | height: 60px;
201 | width: 60px;
202 | }
203 |
204 | .lf-popover-snapshot {
205 | width: 150px;
206 | }
207 | .lf-popover-snapshot h5 {
208 | margin: 5px 0 10px 0;
209 | font-size: 0.75rem;
210 | }
211 | .lf-popover-snapshot a {
212 | display: block;
213 | text-decoration: none;
214 | }
215 | .lf-popover-snapshot a:before {
216 | content: '⥼';
217 | margin-right: 5px;
218 | }
219 | .lf-popover-snapshot .lf-note {
220 | display: block;
221 | margin-top: 10px;
222 | color: #999;
223 | }
224 | .lf-player-controls > div {
225 | margin-right: 5px;
226 | margin-left: 5px;
227 | }
228 | .lf-player-controls > div:first-child {
229 | margin-left: 0px;
230 | }
231 | .lf-player-controls > div:last-child {
232 | margin-right: 0px;
233 | }
234 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export * from './Player';
2 | export * from './Controls';
3 | export * from './Seeker';
4 |
--------------------------------------------------------------------------------
/src/versions.ts:
--------------------------------------------------------------------------------
1 | export const REACT_LOTTIE_PLAYER_VERSION = '3.5.4';
2 | export const LOTTIE_WEB_VERSION = '^5.12.2';
3 |
--------------------------------------------------------------------------------
/stories/index.stories.tsx:
--------------------------------------------------------------------------------
1 | import '@storybook/addon-console';
2 |
3 | import * as React from 'react';
4 | import { useCallback, useRef, useEffect } from 'react';
5 |
6 | import { Controls } from '../src/Controls';
7 | import { Player } from '../src/Player';
8 |
9 | export default {
10 | title: 'Components',
11 | parameters: {
12 | info: { inline: true },
13 | },
14 | };
15 |
16 | export const LottiePlayer = (): any => {
17 | const playerRef = useRef(null);
18 |
19 | const onEvent = useCallback((event: any) => {
20 | console.log('event', event);
21 | }, []);
22 |
23 | useEffect(() => {
24 | if (playerRef !== null && playerRef.current !== null) {
25 | // @ts-ignore: Object is possibly 'null'.
26 | console.log(playerRef.current.getVersions());
27 | console.log('Calling versions()');
28 | }
29 | }, [playerRef]);
30 |
31 | return (
32 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5", // Specify ECMAScript target version
4 | "module": "esnext", // Specify module code generation
5 | "moduleResolution": "node", // Resolve modules using Node.js style
6 | // "allowSyntheticDefaultImports": true, // Allow default imports from modules with no default export
7 | "resolveJsonModule": true, // Include modules imported with .json extension
8 | "jsx": "react", // Support JSX in .tsx files
9 | "lib": ["es5", "es6", "dom"],
10 | // "allowJs": true, // Allow JavaScript
11 | "skipLibCheck": true,
12 | // "esModuleInterop": true,
13 | "sourceMap": true, // Generate corresponding .map file
14 | "declaration": true, // Do not generate corresponding .d.ts file
15 | "forceConsistentCasingInFileNames": true,
16 | "strict": true,
17 | "strictNullChecks": true,
18 | "noImplicitReturns": true, // Report errors on implicit return types
19 | "noImplicitThis": true, // Report errors on implicit this type
20 | "noImplicitAny": true, // Report errors on implicit any types
21 | "noUnusedLocals": true, // Report errors on unused locals
22 | "noUnusedParameters": true, // Report errors on unused parameters
23 | "noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement
24 | "experimentalDecorators": true, // Enables experimental support for ES decorators
25 | "emitDecoratorMetadata": true, // Emit decorator metadata
26 | "importHelpers": false, // Import helper functions from tslib
27 | "preserveConstEnums": true,
28 | "noEmit": false,
29 | "plugins": [],
30 | "declarationDir": ""
31 | },
32 | "include": ["src", "stories"],
33 | "exclude": ["node_modules"]
34 | }
35 |
--------------------------------------------------------------------------------