├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── atropos_core_issue.yml │ ├── atropos_react_issue.yml │ ├── config.yml │ └── feature-request.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── build.yml │ ├── formatting.yml │ ├── issue-close-require.yml │ ├── issue-labeled.yml │ └── lint.yml ├── .gitignore ├── .prettierignore ├── .prettierrc ├── .vscode └── settings.json ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── TODO.md ├── babel.config.js ├── babel.config.react.js ├── babel.config.vue.js ├── build └── package.json ├── package-lock.json ├── package.json ├── package ├── LICENSE ├── README.md └── package.json ├── playground ├── core │ ├── i │ │ ├── atropos-bg.svg │ │ ├── atropos-forest-back.svg │ │ ├── atropos-forest-front.svg │ │ ├── atropos-forest-mid.svg │ │ ├── atropos-logo-en.svg │ │ └── atropos-mountains.svg │ ├── index.html │ └── main.css ├── element │ ├── i │ │ ├── atropos-bg.svg │ │ ├── atropos-forest-back.svg │ │ ├── atropos-forest-front.svg │ │ ├── atropos-forest-mid.svg │ │ ├── atropos-logo-en.svg │ │ └── atropos-mountains.svg │ ├── index.html │ ├── main.css │ └── main.js └── react │ ├── App.jsx │ ├── i │ ├── atropos-bg.svg │ ├── atropos-forest-back.svg │ ├── atropos-forest-front.svg │ ├── atropos-forest-mid.svg │ ├── atropos-logo-en.svg │ └── atropos-mountains.svg │ ├── index.html │ ├── main.css │ └── main.js ├── scripts ├── banner.js ├── build-element.js ├── build-js.js ├── build-react.js ├── build-styles.js ├── build-types.js ├── build.js ├── release.js ├── utils │ ├── autoprefixer.js │ ├── clean-css.js │ ├── fs-extra.js │ └── less.js └── watch.js └── src ├── atropos.d.ts ├── atropos.js ├── atropos.less ├── atropos.scss ├── element ├── atropos-element.d.ts └── atropos-element.js └── react ├── atropos-react.d.ts └── atropos-react.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package 4 | postinstall.js 5 | scripts/release.js 6 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const rules = { 2 | 'no-param-reassign': 'off', 3 | 'import/prefer-default-export': 'off', 4 | 'import/extensions': 'off', 5 | 'import/no-extraneous-dependencies': 'off', 6 | 'no-console': 'off', 7 | 'no-restricted-globals': ['error', 'window', 'document'], 8 | 'react/jsx-filename-extension': 'off', 9 | 'react/prop-types': 'off', 10 | 'react/jsx-props-no-spreading': 'off', 11 | }; 12 | module.exports = { 13 | env: { 14 | browser: true, 15 | es6: true, 16 | node: true, 17 | }, 18 | parserOptions: { 19 | ecmaFeatures: { 20 | jsx: true, 21 | }, 22 | ecmaVersion: 12, 23 | sourceType: 'module', 24 | }, 25 | 26 | overrides: [ 27 | { 28 | files: ['*.js'], 29 | extends: ['plugin:react/recommended', 'airbnb-base', 'plugin:prettier/recommended'], 30 | rules, 31 | }, 32 | { 33 | files: ['**/*.jsx', 'src/react/*.js'], 34 | plugins: ['react'], 35 | rules, 36 | }, 37 | ], 38 | }; 39 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Atropos loves to welcome your contributions. There are several ways to help out: 4 | 5 | - Create an [issue](https://github.com/nolimits4web/atropos/issues) on GitHub, if you have found a bug 6 | - Write test cases or provide examples for open bug issues 7 | - Write patches for open bug/feature issues 8 | 9 | There are a few guidelines that we need contributors to follow so that we have a 10 | chance of keeping on top of things. 11 | 12 | ## Getting Started 13 | 14 | - Make sure you have a [GitHub account](https://github.com/signup/free). 15 | - Submit an [issue](https://github.com/nolimits4web/atropos/issues), assuming one does not already exist. 16 | - Clearly describe the issue including steps to reproduce when it is a bug. 17 | - Make sure you fill in the earliest version that you know has the issue. 18 | - Fork the repository on GitHub. 19 | 20 | ## Making Changes 21 | 22 | - Create a topic branch from where you want to base your work. 23 | - This is usually the master branch. 24 | - Only target release branches if you are certain your fix must be on that 25 | branch. 26 | - To quickly create a topic branch based on master; `git branch master/my_contribution master` then checkout the new branch with `git checkout master/my_contribution`. Better avoid working directly on the 27 | `master` branch, to avoid conflicts if you pull in updates from origin. 28 | - Make commits of logical units. 29 | - Check for unnecessary whitespace with `git diff --check` before committing. 30 | - Use descriptive commit messages and reference the #issue number. 31 | 32 | ## Submitting Changes 33 | 34 | - Push your changes to a topic branch in your fork of the repository. 35 | - Submit a pull request to the repository 36 | 37 | ## Editor Config 38 | 39 | The project uses .editorconfig to define the coding style of each file. We recommend that you install the Editor Config extension for your preferred IDE. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/atropos_core_issue.yml: -------------------------------------------------------------------------------- 1 | name: '🐞 Atropos Core Issue' 2 | description: Report an issue with Atropos 3 | labels: [] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: checkboxes 10 | id: qa 11 | attributes: 12 | label: Check that this is really a bug 13 | description: For Q&A open a [GitHub Discussion](https://github.com/nolimits4web/atropos/discussions) 14 | options: 15 | - label: I confirm 16 | required: true 17 | - type: input 18 | id: reproduction 19 | attributes: 20 | label: Reproduction link 21 | description: Please provide a link to a repo that can reproduce the problem you ran into. A reproduction is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "missing demo" label. If no reproduction is provided after 3 days, it will be auto-closed. 22 | placeholder: 'https://codesandbox.io/..' 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: descr 27 | attributes: 28 | label: Bug description 29 | description: A clear and concise description of what the bug is 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: expected 34 | attributes: 35 | label: Expected Behavior 36 | description: A concise description of what you expected to happen 37 | validations: 38 | required: false 39 | - type: textarea 40 | id: actual 41 | attributes: 42 | label: Actual Behavior 43 | description: A concise description of what you're experiencing 44 | validations: 45 | required: false 46 | - type: input 47 | id: atropos 48 | attributes: 49 | label: Atropos version 50 | description: Exact release version or commit hash 51 | placeholder: e.g 1.0.0 52 | validations: 53 | required: true 54 | - type: input 55 | id: browser 56 | attributes: 57 | label: Platform/Target and Browser Versions 58 | description: Platform client you are targeting such as macOS, Windows, Cordova, iOS, Android, Chrome, etc. 59 | placeholder: e.g macOS Safari 14.1 60 | validations: 61 | required: true 62 | - type: checkboxes 63 | id: checkboxes 64 | attributes: 65 | label: Validations 66 | description: Before submitting the issue, please make sure you do the following 67 | options: 68 | - label: Follow our [Code of Conduct](https://github.com/nolimits4web/atropos/blob/master/CODE_OF_CONDUCT.md) 69 | required: true 70 | - label: Read the [docs](https://atroposjs.com/docs). 71 | required: true 72 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 73 | required: true 74 | - label: Make sure this is a Atropos issue and not a framework-specific issue 75 | required: true 76 | - type: checkboxes 77 | id: pr 78 | attributes: 79 | label: Would you like to open a PR for this bug? 80 | description: Before starting to work on PR it is recommended to get maintainers approval 81 | options: 82 | - label: I'm willing to open a PR 83 | required: false 84 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/atropos_react_issue.yml: -------------------------------------------------------------------------------- 1 | name: '🐞 Atropos React Issue' 2 | description: Create a report for Atropos React components 3 | labels: 'React' 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for taking the time to fill out this bug report! 9 | - type: checkboxes 10 | id: qa 11 | attributes: 12 | label: Check that this is really a bug 13 | description: For Q&A open a [GitHub Discussion](https://github.com/nolimits4web/atropos/discussions) 14 | options: 15 | - label: I confirm 16 | required: true 17 | - type: input 18 | id: reproduction 19 | attributes: 20 | label: Reproduction link 21 | description: Please provide a link to a repo that can reproduce the problem you ran into. A reproduction is required unless you are absolutely sure that the issue is obvious and the provided information is enough to understand the problem. If a report is vague (e.g. just a generic error message) and has no reproduction, it will receive a "missing demo" label. If no reproduction is provided after 3 days, it will be auto-closed. 22 | placeholder: 'https://codesandbox.io/..' 23 | validations: 24 | required: true 25 | - type: textarea 26 | id: descr 27 | attributes: 28 | label: Bug description 29 | description: A clear and concise description of what the bug is 30 | validations: 31 | required: true 32 | - type: textarea 33 | id: expected 34 | attributes: 35 | label: Expected Behavior 36 | description: A concise description of what you expected to happen 37 | validations: 38 | required: false 39 | - type: textarea 40 | id: actual 41 | attributes: 42 | label: Actual Behavior 43 | description: A concise description of what you're experiencing 44 | validations: 45 | required: false 46 | - type: input 47 | id: atropos 48 | attributes: 49 | label: Atropos version 50 | description: Exact release version or commit hash 51 | placeholder: e.g 1.0.0 52 | validations: 53 | required: true 54 | - type: input 55 | id: browser 56 | attributes: 57 | label: Platform/Target and Browser Versions 58 | description: Platform client you are targeting such as macOS, Windows, Cordova, iOS, Android, Chrome, etc. 59 | placeholder: e.g macOS Safari 14.1 60 | validations: 61 | required: true 62 | - type: checkboxes 63 | id: checkboxes 64 | attributes: 65 | label: Validations 66 | description: Before submitting the issue, please make sure you do the following 67 | options: 68 | - label: Follow our [Code of Conduct](https://github.com/nolimits4web/atropos/blob/master/CODE_OF_CONDUCT.md) 69 | required: true 70 | - label: Read the [docs](https://atroposjs.com/docs). 71 | required: true 72 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 73 | required: true 74 | - label: Make sure this is a Atropos issue and not a framework-specific issue 75 | required: true 76 | - type: checkboxes 77 | id: pr 78 | attributes: 79 | label: Would you like to open a PR for this bug? 80 | description: Before starting to work on PR it is recommended to get maintainers approval 81 | options: 82 | - label: I'm willing to open a PR 83 | required: false 84 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: 📃 Documentation Issue 3 | url: https://github.com/nolimits4web/atropos-website/issues/new 4 | about: Issues with Atropos website 5 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: "\U0001F680 New feature proposal" 2 | description: Propose a new feature to be added to Atropos 3 | labels: ['feature request'] 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: | 8 | Thanks for your interest in the project and taking the time to fill out this feature report! 9 | - type: textarea 10 | id: feature-description 11 | attributes: 12 | label: Clear and concise description of the problem 13 | description: As a developer using Atropos I want [goal / wish] so that [benefit] 14 | validations: 15 | required: true 16 | - type: textarea 17 | id: suggested-solution 18 | attributes: 19 | label: Suggested solution 20 | description: 'In module [xy] we could provide following implementation...' 21 | validations: 22 | required: true 23 | - type: textarea 24 | id: alternative 25 | attributes: 26 | label: Alternative 27 | description: Clear and concise description of any alternative solutions or features you've considered. 28 | - type: textarea 29 | id: additional-context 30 | attributes: 31 | label: Additional context 32 | description: Any other context or screenshots about the feature request here. 33 | - type: checkboxes 34 | id: checkboxes 35 | attributes: 36 | label: Validations 37 | description: Before submitting the issue, please make sure you do the following 38 | options: 39 | - label: Follow our [Code of Conduct](https://github.com/nolimits4web/atropos/blob/master/CODE_OF_CONDUCT.md) 40 | required: true 41 | - label: Read the [docs](https://atroposjs.com/docs). 42 | required: true 43 | - label: Check that there isn't already an issue that request the same feature to avoid creating a duplicate. 44 | required: true 45 | - type: checkboxes 46 | id: pr 47 | attributes: 48 | label: Would you like to open a PR for this feature? 49 | description: Before starting to work on PR it is recommended to get maintainers approval. 50 | options: 51 | - label: I'm willing to open a PR 52 | required: false 53 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Describe the big picture of your changes here to communicate to the maintainers why we should accept this pull request. If it fixes a bug or resolves a feature request, be sure to link to that issue. 2 | 3 | The best way to propose a feature is to open an issue first and discuss your ideas there before implementing them. 4 | 5 | Always follow the [contribution guidelines](https://github.com/nolimits4web/atropos/blob/master/CONTRIBUTING.md) when submitting a pull request. 6 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout the repository 14 | uses: actions/checkout@v2 15 | - name: Install Node.js 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: 14 19 | - name: Install dependencies 20 | uses: bahmutov/npm-install@v1 21 | - name: Run build 22 | run: npm run build:prod 23 | -------------------------------------------------------------------------------- /.github/workflows/formatting.yml: -------------------------------------------------------------------------------- 1 | name: Formatting 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout the repository 12 | uses: actions/checkout@v2 13 | - name: Install Node.js 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 14 17 | - name: Install dependencies 18 | uses: bahmutov/npm-install@v1 19 | - name: Run prettier check 20 | run: npm run check-format 21 | -------------------------------------------------------------------------------- /.github/workflows/issue-close-require.yml: -------------------------------------------------------------------------------- 1 | name: Issue Close Require 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | close-issues: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: missing demo 12 | uses: actions-cool/issues-helper@v2.2.1 13 | with: 14 | actions: 'close-issues' 15 | token: ${{ secrets.GITHUB_TOKEN }} 16 | labels: 'missing demo' 17 | inactive-day: 3 18 | -------------------------------------------------------------------------------- /.github/workflows/issue-labeled.yml: -------------------------------------------------------------------------------- 1 | name: Issue Labeled 2 | 3 | on: 4 | issues: 5 | types: [labeled] 6 | 7 | jobs: 8 | reply-labeled: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: missing demo 12 | if: github.event.label.name == 'missing demo' 13 | uses: actions-cool/issues-helper@v2.2.1 14 | with: 15 | actions: 'create-comment, remove-labels' 16 | token: ${{ secrets.GITHUB_TOKEN }} 17 | issue-number: ${{ github.event.issue.number }} 18 | body: | 19 | Hello @${{ github.event.issue.user.login }}. Please provide a online reproduction by [codesandbox](https://codesandbox.io/) or a minimal GitHub repository. Issues labeled by `missing demo` will be closed if no activities in 3 days. 20 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | on: 3 | push: 4 | branches: [master] 5 | pull_request: 6 | branches: [master] 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout the repository 12 | uses: actions/checkout@v2 13 | - name: Install Node.js 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 14 17 | - name: Install dependencies 18 | uses: bahmutov/npm-install@v1 19 | - name: Run eslint 20 | run: npm run lint 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | # Logs 3 | logs 4 | *.log 5 | npm-debug.log* 6 | yarn-debug.log* 7 | yarn-error.log* 8 | lerna-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # TypeScript v1 declaration files 46 | typings/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Microbundle cache 58 | .rpt2_cache/ 59 | .rts2_cache_cjs/ 60 | .rts2_cache_es/ 61 | .rts2_cache_umd/ 62 | 63 | # Optional REPL history 64 | .node_repl_history 65 | 66 | # Output of 'npm pack' 67 | *.tgz 68 | 69 | # Yarn Integrity file 70 | .yarn-integrity 71 | 72 | # dotenv environment variables file 73 | .env 74 | .env.test 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | 93 | # Serverless directories 94 | .serverless/ 95 | 96 | # FuseBox cache 97 | .fusebox/ 98 | 99 | # DynamoDB Local files 100 | .dynamodb/ 101 | 102 | # TernJS port file 103 | .tern-port 104 | 105 | build/*.css 106 | build/*.less 107 | build/*.scss 108 | build/**/*.js 109 | build/**/*.mjs 110 | build/**/*.ts 111 | build/*.map 112 | package/*.css 113 | package/*.less 114 | package/*.scss 115 | package/**/*.js 116 | package/**/*.mjs 117 | package/**/*.ts 118 | package/**/*.map 119 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | build 2 | node_modules 3 | package 4 | .nova 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "htmlWhitespaceSensitivity": "css", 5 | "insertPragma": false, 6 | "jsxBracketSameLine": false, 7 | "jsxSingleQuote": false, 8 | "printWidth": 100, 9 | "proseWrap": "preserve", 10 | "quoteProps": "as-needed", 11 | "requirePragma": false, 12 | "endOfLine": "auto", 13 | "semi": true, 14 | "singleQuote": true, 15 | "tabWidth": 2, 16 | "trailingComma": "all", 17 | "useTabs": false 18 | } 19 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "eslint.enable": true, 4 | "editor.defaultFormatter": "esbenp.prettier-vscode" 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | # [2.0.2](https://github.com/nolimits4web/atropos/compare/v2.0.1...v2.0.2) (2023-07-04) 4 | 5 | ### Bug Fixes 6 | 7 | - **types:** add compatibility with old resolver ([e1d24e9](https://github.com/nolimits4web/atropos/commit/e1d24e9df172bce6271e919f470ea16e4a7889dd)), closes [#41](https://github.com/nolimits4web/atropos/issues/41) 8 | 9 | 10 | ### Features 11 | 12 | - **element:** fallback where adoptedStyleSheets is not supported ([f5a472e](https://github.com/nolimits4web/atropos/commit/f5a472e2efb16c801116efe3dc7b3c786cfbcaeb)) 13 | 14 | 15 | # [2.0.1](https://github.com/nolimits4web/atropos/compare/v1.0.2...v2.0.0) (2023-06-27) 16 | 17 | ### Features 18 | 19 | - add Atropos web component ([cef009b](https://github.com/nolimits4web/atropos/commit/cef009b8816012efbaa9ba08de3267fa5da1ee6d)) 20 | - add element types + refactor package structure ([57108fa](https://github.com/nolimits4web/atropos/commit/57108faf8a2fe5febc247244a4857a40ef076322)) 21 | - remove Vue and Svelte components ([#40](https://github.com/nolimits4web/atropos/issues/40)) ([9850bf7](https://github.com/nolimits4web/atropos/commit/9850bf7ee3b99f4b25be6fbefe55554d12342bd9)) 22 | 23 | # [1.0.2](https://github.com/nolimits4web/atropos/compare/v1.0.1...v1.0.2) (2022-02-17) 24 | 25 | ### Bug Fixes 26 | 27 | - **svelte:** unknown file extension ".svelte" ([dae9ad3](https://github.com/nolimits4web/atropos/commit/dae9ad307b92dcd28091626e25ed63b8ede2ce36)) 28 | - add variable for linux ([f0f9c38](https://github.com/nolimits4web/atropos/commit/f0f9c384d475c87d997aafebd92f35f178e4fca7)) 29 | 30 | # [1.0.1](https://github.com/nolimits4web/atropos/compare/v1.0.0-beta.1...v1.0.1) (2021-10-18) 31 | 32 | - new `alwaysActive` parameter to keep Atropos "activated"/"entered" all the time 33 | - new `stretchZ` parameter to set `translateZ` offset for multiple Atropos elements in same container (with same `eventsEl`) 34 | - new `commonOrigin` parameter for multiple Atropos elements in same container (with same `eventsEl`) 35 | - remove rotateLock functionality in favor of new smooth rotation ([0ba0d06](https://github.com/nolimits4web/atropos/commit/0ba0d06abb8672a4b785b8bd5e743c2b1f7dff4a)) 36 | - removed `durationEnter` parameter 37 | - removed `durationLeave` parameter 38 | - removed `rotateLock` parameter 39 | - added single `duration` parameter (defaults to `300`) 40 | 41 | # [1.0.0-beta.2](https://github.com/nolimits4web/atropos/compare/v1.0.0-beta.1...v1.0.0-beta.2) (2021-10-15) 42 | 43 | ### Features 44 | 45 | - "always active" functionality, `stretchZ` and `commonOrigin` ([78c177c](https://github.com/nolimits4web/atropos/commit/78c177c054504b122116d317c4d7306ab7ce57ad)) 46 | - new `alwaysActive` parameter to keep Atropos "activated"/"entered" all the time 47 | - new `stretchZ` parameter to set `translateZ` offset for multiple Atropos elements in same container (with same `eventsEl`) 48 | - new `commonOrigin` parameter for multiple Atropos elements in same container (with same `eventsEl`) 49 | 50 | # [1.0.0-beta.1](https://github.com/nolimits4web/atropos/compare/v0.11.2...v1.0.0-beta.1) (2021-10-15) 51 | 52 | ### Features 53 | 54 | - remove rotateLock functionality in favor of new smooth rotation ([0ba0d06](https://github.com/nolimits4web/atropos/commit/0ba0d06abb8672a4b785b8bd5e743c2b1f7dff4a)) 55 | - removed `durationEnter` parameter 56 | - removed `durationLeave` parameter 57 | - removed `rotateLock` parameter 58 | - added single `duration` parameter (defaults to `300`) 59 | 60 | # [0.11.2](https://github.com/nolimits4web/atropos/compare/v0.11.1...v0.11.2) (2021-10-15) 61 | 62 | ### Bug Fixes 63 | 64 | - reset eventsEl bounding rect on pointer leave ([ce09fdb](https://github.com/nolimits4web/atropos/commit/ce09fdbce45f6b0c39ee2817f4997df17675e98d)) 65 | 66 | # [0.11.1](https://github.com/nolimits4web/atropos/compare/v0.11.0...v0.11.1) (2021-10-15) 67 | 68 | ### Bug Fixes 69 | 70 | - **react:** add `stretchX` and `stretchY` props ([5d06bd9](https://github.com/nolimits4web/atropos/commit/5d06bd93eb1ced5d0dd6a92e2097920166f939ac)) 71 | 72 | # [0.11.0](https://github.com/nolimits4web/atropos/compare/v0.10.1...v0.11.0) (2021-10-15) 73 | 74 | ### Features 75 | 76 | - transform based on eventsEl (if specified) + stretchX/Y parameters ([b5c59c7](https://github.com/nolimits4web/atropos/commit/b5c59c786cf0a91b8518c4260e394ab9ee20c9e0)) 77 | 78 | # [0.10.1](https://github.com/nolimits4web/atropos/compare/v0.10.0...v0.10.1) (2021-10-12) 79 | 80 | ### Bug Fixes 81 | 82 | - **svelte:** add default value for `class` prop ([2d05998](https://github.com/nolimits4web/atropos/commit/2d0599880870c19364752f9454c33ad4bac2c316)) 83 | 84 | # [0.10.0](https://github.com/nolimits4web/atropos/compare/v0.9.6...v0.10.0) (2021-10-12) 85 | 86 | ### Features 87 | 88 | - Atropos Svelte component ([4efc4b3](https://github.com/nolimits4web/atropos/commit/4efc4b3f3d6848ceb77611f61a75e8a05b9112e8)), closes [#6](https://github.com/nolimits4web/atropos/issues/6) 89 | 90 | # [0.9.6](https://github.com/nolimits4web/atropos/compare/v0.9.5...v0.9.6) (2021-09-28) 91 | 92 | ### Bug Fixes 93 | 94 | - add missing styles from SCSS ([8c17306](https://github.com/nolimits4web/atropos/commit/8c173067435085001735ec15028095b9d2b91deb)) 95 | 96 | # [0.9.5](https://github.com/nolimits4web/atropos/compare/v0.9.4...v0.9.5) (2021-09-28) 97 | 98 | ### Features 99 | 100 | - `rotateTouch` now accepts `scroll-x` and `scroll-y` values to keep scrolling on touchscreens ([c13dfb7](https://github.com/nolimits4web/atropos/commit/c13dfb7204be1b3972478b1bdf40973884f4aa5f)) 101 | 102 | # [0.9.4](https://github.com/nolimits4web/atropos/compare/v0.9.3...v0.9.4) (2021-09-24) 103 | 104 | ### Bug Fixes 105 | 106 | - fix UMD version ([524756d](https://github.com/nolimits4web/atropos/commit/524756d359e38d41d6e6ec9e9e07e76c3299c33b)) 107 | 108 | # [0.9.3] (2021-07-12) 109 | 110 | Initial release 111 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | - Using welcoming and inclusive language 18 | - Being respectful of differing viewpoints and experiences 19 | - Gracefully accepting constructive criticism 20 | - Focusing on what is best for the community 21 | - Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | - The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | - Trolling, insulting/derogatory comments, and personal or political attacks 28 | - Public or private harassment 29 | - Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | - Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting Vladimir Kharlampidi . All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Atropos loves to welcome your contributions. There are several ways to help out: 4 | 5 | - Create an [issue](https://github.com/nolimits4web/atropos/issues) on GitHub, if you have found a bug 6 | - Write test cases or provide examples for open bug issues 7 | - Write patches for open bug/feature issues 8 | 9 | There are a few guidelines that we need contributors to follow so that we have a 10 | chance of keeping on top of things. 11 | 12 | ## Getting Started 13 | 14 | - Make sure you have a [GitHub account](https://github.com/signup/free). 15 | - Submit an [issue](https://github.com/nolimits4web/atropos/issues), assuming one does not already exist. 16 | - Clearly describe the issue including steps to reproduce when it is a bug. 17 | - Make sure you fill in the earliest version that you know has the issue. 18 | - Fork the repository on GitHub. 19 | 20 | ## Making Changes 21 | 22 | - Create a topic branch from where you want to base your work. 23 | - This is usually the master branch. 24 | - Only target release branches if you are certain your fix must be on that 25 | branch. 26 | - To quickly create a topic branch based on master; `git branch master/my_contribution master` then checkout the new branch with `git checkout master/my_contribution`. Better avoid working directly on the 27 | `master` branch, to avoid conflicts if you pull in updates from origin. 28 | - Make commits of logical units. 29 | - Check for unnecessary whitespace with `git diff --check` before committing. 30 | - Use descriptive commit messages and reference the #issue number. 31 | 32 | ## Submitting Changes 33 | 34 | - Push your changes to a topic branch in your fork of the repository. 35 | - Submit a pull request to the repository 36 | 37 | ## Editor Config 38 | 39 | The project uses .editorconfig to define the coding style of each file. We recommend that you install the Editor Config extension for your preferred IDE. 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vladimir Kharlampidi 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 |

2 | 3 |

4 | 5 | # Atropos 6 | 7 | Atropos is a lightweight, free and open-source JavaScript library to create stunning touch-friendly 3D parallax hover effects. 8 | 9 | Available for JavaScript, React and as a Web Component 10 | 11 | ## Community 12 | 13 | The Atropos community can be found on [GitHub Discussions](https://github.com/nolimits4web/atropos/discussions), where you can ask questions, voice ideas, and share your projects 14 | 15 | Our [Code of Conduct](https://github.com/nolimits4web/atropos/blob/master/CODE_OF_CONDUCT.md) applies to all Atropos community channels. 16 | 17 | ## Dist / Build 18 | 19 | On production use files (JS and CSS) only from `package/` folder, there will be the most stable versions, `build/` folder is only for development purpose. 20 | 21 | ### Development Build 22 | 23 | Install all dependencies, in repo's root: 24 | 25 | ``` 26 | 27 | $ npm install 28 | 29 | ``` 30 | 31 | And build development version of Atropos: 32 | 33 | ``` 34 | 35 | $ npm run build:dev 36 | 37 | ``` 38 | 39 | The result is available in `build/` folder. 40 | 41 | ### Running demos: 42 | 43 | All demos located in `./playground` folder. There you will find Core (HTML, JS), React and Angular versions. 44 | To open demo, run: 45 | 46 | - **Core**: `npm run core` 47 | - **React**: `npm run react` 48 | 49 | ### Production Build 50 | 51 | ``` 52 | 53 | $ npm run build:prod 54 | 55 | ``` 56 | 57 | Production version will available in `package/` folder. 58 | 59 | ## Contributing 60 | 61 | All changes should be committed to `src/` files only. Before you open an issue please review the [contributing](https://github.com/nolimits4web/atropos/blob/master/CONTRIBUTING.md) guideline. 62 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - core typings 2 | - react typings 3 | 4 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | let modules = process.env.MODULES || false; 2 | if (modules === 'esm' || modules === 'false') modules = false; 3 | 4 | module.exports = { 5 | presets: [['@babel/preset-env', { modules, loose: true }]], 6 | }; 7 | -------------------------------------------------------------------------------- /babel.config.react.js: -------------------------------------------------------------------------------- 1 | let modules = process.env.MODULES || false; 2 | if (modules === 'esm' || modules === 'false') modules = false; 3 | 4 | module.exports = { 5 | presets: ['@babel/preset-react', ['@babel/preset-env', { modules, loose: true }]], 6 | }; 7 | -------------------------------------------------------------------------------- /babel.config.vue.js: -------------------------------------------------------------------------------- 1 | let modules = process.env.MODULES || false; 2 | if (modules === 'esm' || modules === 'false') modules = false; 3 | 4 | module.exports = { 5 | presets: [['@babel/preset-env', { modules, loose: true }]], 6 | }; 7 | -------------------------------------------------------------------------------- /build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atropos-build", 3 | "version": "0.0.0", 4 | "types": "atropos.d.ts", 5 | "type": "module", 6 | "exports": { 7 | ".": "./atropos.mjs", 8 | "./react": "./atropos-react.mjs", 9 | "./atropos.js": "./atropos.js", 10 | "./atropos.min.js": "./atropos.min.js", 11 | "./atropos.css": "./atropos.css", 12 | "./atropos.min.css": "./atropos.min.css", 13 | "./atropos.less": "./atropos.less", 14 | "./atropos.scss": "./atropos.scss", 15 | "./element": "./atropos-element.mjs" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atropos-src", 3 | "version": "2.0.2", 4 | "description": "Touch-friendly 3D parallax hover effects", 5 | "scripts": { 6 | "/*============ Build ============*/": "============", 7 | "build:dev": "cross-env NODE_ENV=development node scripts/build", 8 | "build:prod": "cross-env NODE_ENV=production node scripts/build", 9 | "/*============ Watch ============*/": "============", 10 | "watch:dev": "cross-env NODE_ENV=development node scripts/watch", 11 | "watch:prod": "cross-env NODE_ENV=production node scripts/watch", 12 | "/*============ Playground ============*/": "============", 13 | "core": "npm run build:dev && concurrently --kill-others \"vite ./playground/core\" \"npm run watch:dev\" ", 14 | "react": "npm run build:dev && concurrently --kill-others \"vite ./playground/react\" \"npm run watch:dev\"", 15 | "element": "npm run build:dev && concurrently --kill-others \"vite ./playground/element\" \"npm run watch:dev\"", 16 | "/*============ Tooling ============*/": "============", 17 | "prettier": "prettier \"**/*.+(js|json|scss|css|less|ts|jsx)\"", 18 | "format": "npm run prettier -- --write", 19 | "check-format": "npm run prettier -- --list-different", 20 | "lint": "eslint --ext .js,.jsx .", 21 | "validate": "npm-run-all --parallel check-format lint", 22 | "release": "npm run validate && node ./scripts/release", 23 | "changelog": "npx conventional-changelog -p angular -i CHANGELOG.md -u -s", 24 | "test": "npm run validate && npm run build:prod" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "git+https://github.com/nolimits4web/atropos.git" 29 | }, 30 | "author": "", 31 | "license": "MIT", 32 | "bugs": { 33 | "url": "https://github.com/nolimits4web/atropos/issues" 34 | }, 35 | "homepage": "https://atroposjs.com", 36 | "devDependencies": { 37 | "@babel/cli": "^7.22.5", 38 | "@babel/core": "^7.22.5", 39 | "@babel/preset-env": "^7.22.5", 40 | "@babel/preset-react": "^7.22.5", 41 | "@rollup/plugin-babel": "^6.0.3", 42 | "@rollup/plugin-node-resolve": "^15.1.0", 43 | "@rollup/plugin-replace": "^5.0.2", 44 | "autoprefixer": "^10.4.14", 45 | "clean-css": "^5.3.2", 46 | "concurrently": "^8.2.0", 47 | "cross-env": "^7.0.3", 48 | "eslint": "^8.43.0", 49 | "eslint-config-airbnb-base": "^15.0.0", 50 | "eslint-config-prettier": "^8.8.0", 51 | "eslint-plugin-import": "^2.27.5", 52 | "eslint-plugin-prettier": "^4.2.1", 53 | "eslint-plugin-react": "^7.32.2", 54 | "eslint-plugin-react-hooks": "^4.6.0", 55 | "exec-sh": "^0.4.0", 56 | "fs-extra": "^11.1.1", 57 | "inquirer": "^9.2.7", 58 | "less": "^4.1.3", 59 | "npm-run-all": "^4.1.5", 60 | "postcss": "^8.4.24", 61 | "prettier": "^2.8.8", 62 | "react": "^18.2.0", 63 | "react-dom": "^18.2.0", 64 | "rollup": "^3.25.3", 65 | "serve": "^14.2.0", 66 | "terser": "^5.18.1", 67 | "vite": "^4.3.9" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /package/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Vladimir Kharlampidi 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 | -------------------------------------------------------------------------------- /package/README.md: -------------------------------------------------------------------------------- 1 | # Atropos 2 | 3 | Atropos is a lightweight, free and open-source JavaScript library to create stunning touch-friendly 3D parallax hover effects. 4 | Available for JavaScript, React and as a Web Component 5 | 6 | ## Getting Started 7 | 8 | - [Atropos Website](https://atroposjs.com/) 9 | - [Documentation](https://atroposjs.com/docs) 10 | -------------------------------------------------------------------------------- /package/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "atropos", 3 | "version": "2.0.2", 4 | "description": "Touch-friendly 3D parallax hover effects", 5 | "types": "atropos.d.ts", 6 | "type": "module", 7 | "exports": { 8 | ".": { 9 | "types": "./atropos.d.ts", 10 | "default": "./atropos.mjs" 11 | }, 12 | "./react": { 13 | "types": "./atropos-react.d.ts", 14 | "default": "./atropos-react.mjs" 15 | }, 16 | "./element": { 17 | "types": "./atropos-element.d.ts", 18 | "default": "./atropos-element.mjs" 19 | }, 20 | "./atropos.js": { 21 | "types": "./atropos.d.ts", 22 | "default": "./atropos.js" 23 | }, 24 | "./atropos.min.js": { 25 | "types": "./atropos.d.ts", 26 | "default": "./atropos.min.js" 27 | }, 28 | "./css": "./atropos.css", 29 | "./css/min": "./atropos.min.css", 30 | "./less": "./atropos.less", 31 | "./scss": "./atropos.scss", 32 | "./atropos.css": "./atropos.css", 33 | "./atropos.min.css": "./atropos.min.css", 34 | "./atropos.less": "./atropos.less", 35 | "./atropos.scss": "./atropos.scss", 36 | "./package.json": "./package.json" 37 | }, 38 | "typesVersions": { 39 | "*": { 40 | "react": [ 41 | "./atropos-react.d.ts" 42 | ], 43 | ".": [ 44 | "./atropos.d.ts" 45 | ] 46 | } 47 | }, 48 | "repository": { 49 | "type": "git", 50 | "url": "https://github.com/nolimits4web/atropos.git" 51 | }, 52 | "keywords": [ 53 | "atropos", 54 | "effect", 55 | "parallax", 56 | "hover", 57 | "3d", 58 | "react", 59 | "javascript", 60 | "portfolio", 61 | "gallery", 62 | "showcase" 63 | ], 64 | "author": "Vladimir Kharlampidi", 65 | "license": "MIT", 66 | "bugs": { 67 | "url": "https://github.com/nolimits4web/atropos/issues" 68 | }, 69 | "homepage": "https://atroposjs.com", 70 | "engines": { 71 | "node": ">= 12.0.0" 72 | }, 73 | "releaseDate": "July 4, 2023" 74 | } 75 | -------------------------------------------------------------------------------- /playground/core/i/atropos-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /playground/core/i/atropos-logo-en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/core/i/atropos-mountains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /playground/core/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Atropos Core 8 | 9 | 10 | 11 | 12 |

Lorem ipsum, dolor sit amet consectetur adipisicing elit. Necessitatibus, corporis illum voluptas unde voluptatem 13 | placeat nesciunt, consequatur modi repudiandae dolorem ipsa eius neque non, impedit illo consectetur eveniet? 14 | Doloribus, assumenda?

15 |

Ad ipsum, nulla possimus quas accusantium libero quis placeat nesciunt rem exercitationem, totam at minima magni? 16 | Veniam ipsam perferendis deserunt voluptatibus unde perspiciatis laboriosam. Culpa error deserunt recusandae dolor 17 | neque!

18 |

Deserunt temporibus recusandae reiciendis eos non provident odio, ex excepturi? Qui, itaque aperiam. Qui molestias 19 | eos quidem tempore possimus, sunt dolor ad nemo ea reiciendis, dolore magnam architecto obcaecati repellat.

20 |

Quis omnis amet, vero aspernatur in beatae aliquam. Harum optio adipisci modi tempora rerum! Deserunt, dolorum sed 21 | possimus qui ad officia laboriosam magnam, quia rem dignissimos cupiditate quis corrupti exercitationem!

22 |

Ipsa culpa maxime esse illum sed placeat, obcaecati consequatur assumenda possimus voluptate in quaerat veritatis 23 | numquam ab ea soluta officiis? Necessitatibus autem nobis deserunt, illo ratione voluptatem neque nesciunt. Minus. 24 |

25 |

Aliquid voluptatem non ut, mollitia repellat numquam sint incidunt libero cumque facere est, porro, similique 26 | itaque id. Pariatur tempore quam perferendis deserunt provident, maiores ipsa esse fuga magnam sequi vel!

27 |

Velit est rerum cupiditate eum error placeat, architecto, unde vitae, totam temporibus provident praesentium? 28 | Pariatur accusamus dolorum aspernatur at quas obcaecati vitae minima, sint odit, sit debitis tenetur vel. Modi!

29 |

Assumenda, cupiditate quae! Porro impedit autem ab quibusdam fuga ea assumenda odio repudiandae eius, laudantium 30 | vitae, qui dolorem obcaecati, tenetur dolor necessitatibus repellendus quia quod atque illo. Architecto, sed vero? 31 |

32 |

Voluptatibus vero repellendus, mollitia, eos voluptatem voluptas sit porro labore est delectus dolorum voluptatum 33 | nemo ratione officia dolores in suscipit alias. Tempore deserunt molestias neque saepe, rem omnis quae impedit.

34 |

Deleniti ducimus maiores ex? Repellendus minus vero vitae ut laudantium possimus rerum natus laborum eligendi. 35 | Ipsam laudantium accusantium quisquam veritatis doloremque nihil, cumque distinctio quidem recusandae magni rerum 36 | aliquam illum.

37 |

Unde expedita quas officiis ipsum dicta quisquam, minus ducimus rerum. Deserunt qui accusantium, sint quibusdam, 38 | architecto repellendus commodi, eveniet sit distinctio fugiat similique cupiditate quidem eligendi voluptatum labore 39 | illum repudiandae!

40 |

Pariatur beatae, corporis ea placeat id perspiciatis amet suscipit dolore, recusandae maiores facilis quae soluta 41 | fugiat veritatis laborum nihil, sapiente optio! Repudiandae quas tempore accusantium dignissimos ipsa libero dolorem 42 | suscipit?

43 |

Qui distinctio odio reprehenderit ipsum saepe, quod asperiores. Iusto velit autem impedit qui laboriosam eos ipsum 44 | iste nobis excepturi quod aut, quisquam possimus perferendis, quidem dolorum vero dolore eius. Omnis!

45 |

Natus nihil doloribus possimus placeat fugit debitis a sit delectus unde voluptatem aliquam quod asperiores, libero 46 | veniam corrupti harum vero dicta necessitatibus. Doloremque cupiditate hic dolorem quia. Nam, temporibus eum.

47 |

Aperiam corrupti, eaque quidem sequi nihil porro quasi rem! Aliquam rem consectetur quos animi asperiores incidunt 48 | nulla iure accusantium nemo corporis tempora fugit voluptates neque, odio blanditiis quis consequuntur nihil!

49 |

Sunt necessitatibus tempore doloremque debitis possimus aliquid, eum ex distinctio magni nesciunt deserunt ipsa 50 | atque exercitationem recusandae beatae repellat autem dignissimos rem. Autem temporibus aliquam dignissimos sed 51 | dolorum rerum aliquid.

52 |

Unde atque aliquid iusto praesentium accusantium quae, nulla sunt voluptates! Voluptas molestias suscipit earum 53 | accusamus sunt tenetur alias, et quia eum corrupti illum omnis vel voluptate adipisci nemo repellat. Modi!

54 |

Veniam quaerat aut quo nesciunt tempore, rem soluta. Alias illum, natus quia atque officiis iusto non fuga 55 | repellendus sequi tempore distinctio inventore, quis, perferendis doloremque ad eos quibusdam magni sapiente.

56 |

Incidunt architecto quo accusamus velit, blanditiis tempore adipisci recusandae voluptatibus facere reprehenderit 57 | nemo corporis, hic consequuntur. Nulla, voluptatibus. Cumque omnis esse atque dolore nam perspiciatis officiis 58 | quaerat beatae veritatis maiores.

59 |

Culpa laboriosam nulla laborum molestiae incidunt quasi ex magnam vero earum nemo distinctio assumenda facere nihil 60 | beatae iure, voluptates rem sunt odio voluptate minus quidem velit hic nisi? Harum, necessitatibus?

61 |

Quae, deserunt fugiat laboriosam laudantium nobis consequatur aliquid ex temporibus dolor maiores cum accusamus 62 | commodi eaque eligendi, velit beatae. Soluta consectetur illo repellendus nobis amet molestiae doloremque assumenda 63 | cum corrupti.

64 |

Veritatis numquam blanditiis exercitationem adipisci deserunt quia modi, omnis voluptate molestiae tempora, 65 | doloribus soluta ullam cum possimus voluptatum. Praesentium aliquid possimus iure excepturi consequatur minus iste 66 | odit tempora quas ducimus?

67 |

Quo dolorem, consequuntur laboriosam vel molestias iusto? Cum incidunt minima impedit odio repellat suscipit 68 | ratione dolorum rerum magnam? Eos qui possimus incidunt adipisci praesentium illum excepturi perferendis aut 69 | sapiente? Recusandae?

70 |

Blanditiis, libero commodi. Expedita similique illum quibusdam laborum sit, sed aliquam at modi, assumenda 71 | molestiae veniam minima velit, explicabo placeat. Ad doloremque aliquid quibusdam dolores reiciendis nisi iure 72 | minima tempore?

73 |

Sequi, expedita hic quis fuga eaque id libero dignissimos, asperiores voluptatum possimus deleniti iste vel 74 | mollitia commodi recusandae repellat unde enim, nemo aliquam at repellendus laboriosam delectus! Quae, at 75 | voluptatum.

76 |

Magnam ullam, eveniet nisi animi reiciendis adipisci aliquid, quibusdam quia totam molestias nihil inventore 77 | cupiditate dolores, illum debitis tempora aspernatur commodi quae molestiae culpa nulla neque. Saepe corrupti 78 | distinctio hic?

79 |

Aperiam, nisi itaque officia eveniet numquam nobis omnis ut dolor molestiae asperiores ad neque blanditiis 80 | voluptatibus. Eligendi expedita ipsam tempora, magnam quo alias vero! Facere veniam consectetur accusantium error 81 | debitis.

82 |

Totam obcaecati iste illo corporis? Iusto ipsa magni facere placeat odit voluptatum, ratione error. Beatae minima 83 | veritatis provident numquam voluptate laboriosam necessitatibus neque ab quis? Vero sapiente quisquam qui possimus. 84 |

85 |

Ullam unde doloribus earum temporibus quam minima consequatur cupiditate eligendi, illo numquam nulla porro maxime 86 | id officiis officia ad magni eius dolorem aliquam! Voluptatibus magni esse libero, aspernatur laudantium nemo!

87 |

Dolor quisquam accusantium sit nobis aliquam voluptatem, cupiditate veritatis? Molestiae ad earum obcaecati quae a 88 | excepturi atque, odit consequuntur dignissimos quaerat hic quo repellat ea quam possimus molestias iure nisi!

89 |

Quisquam reiciendis necessitatibus aspernatur, doloribus amet velit asperiores dolores architecto error voluptas 90 | cumque perferendis, consequuntur maiores magni vitae debitis aut explicabo mollitia adipisci modi? Quod ipsa est 91 | exercitationem alias blanditiis!

92 |

Numquam quo minus voluptates quam hic asperiores ad doloremque quis! Mollitia, animi assumenda voluptatibus labore 93 | inventore necessitatibus quae sapiente nulla consequatur facilis qui rerum quod delectus tempora aperiam. Quia, 94 | illum?

95 |

Magnam ea ullam quas accusamus nulla minus, distinctio commodi nemo qui totam mollitia consequatur ducimus 96 | perspiciatis vero aspernatur eligendi culpa error neque, harum laudantium veniam dignissimos nobis! Molestias, fugit 97 | quos.

98 |

Ipsa dolores itaque sequi laboriosam sint, aliquid laudantium accusantium, quo excepturi est dignissimos eius quae 99 | ex modi ratione eum perferendis, ipsum esse illo qui earum. Porro nemo ea nisi odio.

100 |

Neque sed perspiciatis animi laboriosam quam magni libero possimus itaque, atque beatae, rem facilis nobis natus, 101 | quaerat ab error consequatur cupiditate aliquid labore numquam assumenda. Tempore, laboriosam. Necessitatibus, earum 102 | laudantium?

103 |

Eum laborum tempore, ullam ipsam praesentium corporis excepturi veniam sapiente consectetur! Non aliquam vel 104 | voluptate illum. Voluptas quod doloremque impedit eum minus. Quaerat perspiciatis animi, sunt minus perferendis ipsa 105 | alias!

106 |

Deleniti quia perferendis blanditiis praesentium quibusdam itaque totam, odit cupiditate voluptate at deserunt 107 | soluta vero. Aspernatur laboriosam et architecto odio beatae ratione reprehenderit autem laborum, nulla deserunt hic 108 | ipsa cum.

109 |

Nam sapiente velit, nemo sed corrupti quibusdam. Vel excepturi reprehenderit ipsa! Aspernatur reiciendis, quaerat 110 | neque nemo necessitatibus aliquid quo consectetur voluptatibus? Voluptates voluptatibus sequi, voluptate quas 111 | reprehenderit quia qui eos.

112 |

Mollitia aut, minima aperiam impedit amet assumenda! Iure eius animi ratione temporibus molestias magni vero 113 | officia nemo nobis dolores maxime alias nam, sint in laudantium fugiat nesciunt eligendi porro corrupti?

114 |

Nesciunt quia voluptates earum voluptate reprehenderit architecto vel itaque quasi excepturi nobis quisquam nulla 115 | necessitatibus veniam numquam aspernatur eos dicta voluptas, expedita ipsam harum laboriosam, voluptatem molestias 116 | blanditiis. Accusamus, temporibus.

117 |

Dignissimos eaque quia illum perferendis fuga, molestiae voluptate inventore sapiente ut est unde! Modi at 118 | accusamus voluptates recusandae alias quam libero ea earum omnis, adipisci veniam ipsam eaque distinctio sequi.

119 |

Odio laboriosam ab dolore quas voluptatum aut id porro mollitia dolores perferendis in, aspernatur sequi impedit 120 | rem aliquam magni repellendus sit deserunt fugit expedita delectus fugiat nemo quibusdam? Officiis, laborum.

121 |

Doloremque velit optio mollitia iusto, ullam repellendus alias? Veniam perferendis tenetur deserunt vero unde porro 122 | sint voluptas eligendi earum? Architecto voluptates optio unde explicabo voluptatum esse debitis velit temporibus 123 | voluptas!

124 |

Ipsam ullam corrupti molestias, minus, in fugiat illo non architecto commodi similique quis officia. Sit, 125 | perspiciatis doloremque. Dolore inventore voluptates sunt, pariatur facere quidem saepe error consequatur optio. 126 | Est, placeat.

127 |

Deserunt sequi temporibus in incidunt quia voluptas non, laborum veniam tempore delectus, fugit dolores voluptate 128 | maxime at aliquid illum. Repudiandae eos vel tempore exercitationem tempora praesentium ea obcaecati aut 129 | perferendis.

130 |

Veritatis repellat nemo maxime recusandae qui, officiis impedit ipsa ipsum velit exercitationem beatae ratione 131 | porro odio temporibus incidunt vitae ex quam eius, voluptates in aspernatur earum repudiandae alias. Nihil, natus? 132 |

133 |

Ab, iure dolore. A provident officia eaque nesciunt corrupti iusto excepturi omnis cumque labore voluptatibus? 134 | Eveniet ad dignissimos vel a blanditiis sunt quae accusantium magnam, dolorem, in enim harum sapiente?

135 |

Quo quos ipsa asperiores cupiditate illum nemo, ipsam harum animi culpa natus pariatur saepe et alias dolores nulla 136 | modi, sapiente perferendis. Perferendis fugit velit laudantium vero atque molestiae sequi repellat.

137 |

Asperiores rem doloribus maxime nihil, aliquam eveniet magnam accusantium dolorem harum accusamus nemo nobis 138 | aperiam libero tempora quas laboriosam quaerat. Laboriosam, ullam quia quae magni obcaecati culpa repellat ut? 139 | Deserunt.

140 |

Voluptatem dolorem at deleniti quos, corrupti iure impedit fuga praesentium aperiam cum quas quis nostrum eaque 141 | cupiditate porro molestiae nihil vero facilis mollitia. Nisi nulla maxime corporis harum earum doloribus.

142 |
143 |
144 |
145 |
146 |
147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 |
155 |
156 |
157 |
158 |
159 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /playground/core/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 7 | 'Open Sans', 'Helvetica Neue', sans-serif; 8 | min-height: 100vh; 9 | } 10 | body { 11 | background-image: linear-gradient(to bottom, #ad3ef5, #5814a2); 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | .container { 18 | padding: 0px 40px; 19 | max-width: 960px; 20 | margin: 32px auto; 21 | display: flex; 22 | justify-content: space-between; 23 | width: 100%; 24 | box-sizing: border-box; 25 | } 26 | .atropos-banner { 27 | width: 100%; 28 | } 29 | .atropos-banner .atropos-inner { 30 | border-radius: 10px; 31 | } 32 | .atropos-banner img { 33 | position: absolute; 34 | left: -5%; 35 | top: -5%; 36 | width: 110%; 37 | height: 110%; 38 | object-fit: contain; 39 | display: block; 40 | z-index: 1; 41 | transform-style: preserve-3d; 42 | pointer-events: none; 43 | } 44 | .atropos-banner img.atropos-banner-spacer { 45 | position: relative; 46 | width: 100%; 47 | height: auto; 48 | left: 0; 49 | top: 0; 50 | visibility: hidden; 51 | } 52 | .atropos-banner .atropos-shadow { 53 | filter: blur(50px); 54 | opacity: 0.5; 55 | } 56 | .atropos-banner .atropos-highlight { 57 | z-index: 100; 58 | } 59 | 60 | .atropos-banner-text { 61 | position: absolute; 62 | color: #fff; 63 | font-weight: bold; 64 | left: 0%; 65 | top: 0%; 66 | } 67 | .atropos-active .atropos-banner-text { 68 | } 69 | -------------------------------------------------------------------------------- /playground/element/i/atropos-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /playground/element/i/atropos-logo-en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/element/i/atropos-mountains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /playground/element/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Atropos Element 8 | 9 | 10 | 11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /playground/element/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 7 | 'Open Sans', 'Helvetica Neue', sans-serif; 8 | min-height: 100vh; 9 | } 10 | body { 11 | background-image: linear-gradient(to bottom, #ad3ef5, #5814a2); 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | .container { 18 | padding: 0px 40px; 19 | max-width: 960px; 20 | margin: 32px auto; 21 | display: flex; 22 | justify-content: space-between; 23 | width: 100%; 24 | box-sizing: border-box; 25 | } 26 | .atropos-banner { 27 | width: 100%; 28 | } 29 | /* .atropos-banner .atropos-inner { 30 | border-radius: 10px; 31 | } */ 32 | .atropos-banner atropos-component::part(inner) { 33 | border-radius: 10px; 34 | } 35 | .atropos-banner img { 36 | position: absolute; 37 | left: -5%; 38 | top: -5%; 39 | width: 110%; 40 | height: 110%; 41 | object-fit: contain; 42 | display: block; 43 | z-index: 1; 44 | transform-style: preserve-3d; 45 | pointer-events: none; 46 | } 47 | .atropos-banner img.atropos-banner-spacer { 48 | position: relative; 49 | width: 100%; 50 | height: auto; 51 | left: 0; 52 | top: 0; 53 | visibility: hidden; 54 | } 55 | .atropos-banner .atropos-shadow { 56 | filter: blur(50px); 57 | opacity: 0.5; 58 | } 59 | .atropos-banner .atropos-highlight { 60 | z-index: 100; 61 | } 62 | 63 | .atropos-banner-text { 64 | position: absolute; 65 | color: #fff; 66 | font-weight: bold; 67 | left: 0%; 68 | top: 0%; 69 | } 70 | .atropos-active .atropos-banner-text { 71 | } 72 | -------------------------------------------------------------------------------- /playground/element/main.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | import AtroposComponent from '../../build/atropos-element.mjs'; 3 | 4 | customElements.define('atropos-component', AtroposComponent); 5 | -------------------------------------------------------------------------------- /playground/react/App.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Atropos from '../../build/atropos-react.mjs'; 3 | 4 | const App = () => { 5 | return ( 6 |
7 | console.log('enter')}> 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | ); 18 | }; 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /playground/react/i/atropos-bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /playground/react/i/atropos-logo-en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /playground/react/i/atropos-mountains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /playground/react/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Atropos React 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /playground/react/main.css: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | position: relative; 4 | margin: 0; 5 | padding: 0; 6 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 7 | 'Open Sans', 'Helvetica Neue', sans-serif; 8 | min-height: 100vh; 9 | } 10 | body { 11 | background-image: linear-gradient(to bottom, #ad3ef5, #5814a2); 12 | display: flex; 13 | flex-direction: column; 14 | align-items: center; 15 | justify-content: center; 16 | } 17 | #app { 18 | display: contents; 19 | } 20 | .container { 21 | padding: 0px 40px; 22 | max-width: 960px; 23 | margin: 32px auto; 24 | display: flex; 25 | justify-content: space-between; 26 | width: 100%; 27 | box-sizing: border-box; 28 | } 29 | .atropos-banner { 30 | width: 100%; 31 | } 32 | .atropos-banner .atropos-inner { 33 | border-radius: 10px; 34 | } 35 | .atropos-banner img { 36 | position: absolute; 37 | left: -5%; 38 | top: -5%; 39 | width: 110%; 40 | height: 110%; 41 | object-fit: contain; 42 | display: block; 43 | z-index: 1; 44 | transform-style: preserve-3d; 45 | pointer-events: none; 46 | } 47 | .atropos-banner .atropos-banner-spacer { 48 | position: relative; 49 | width: 100%; 50 | height: auto; 51 | left: 0; 52 | top: 0; 53 | visibility: hidden; 54 | } 55 | .atropos-banner .atropos-shadow { 56 | filter: blur(50px); 57 | opacity: 0.5; 58 | } 59 | .atropos-banner .atropos-highlight { 60 | z-index: 100; 61 | } 62 | 63 | .atropos-banner-text { 64 | position: absolute; 65 | color: #fff; 66 | font-weight: bold; 67 | left: 0%; 68 | top: 0%; 69 | } 70 | .atropos-active .atropos-banner-text { 71 | } 72 | -------------------------------------------------------------------------------- /playground/react/main.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | // eslint-disable-next-line 5 | import '../../build/atropos.css'; 6 | 7 | import App from './App.jsx'; 8 | 9 | // eslint-disable-next-line 10 | ReactDOM.render(React.createElement(App), document.getElementById('app')); 11 | -------------------------------------------------------------------------------- /scripts/banner.js: -------------------------------------------------------------------------------- 1 | const pkg = require('../package.json'); 2 | 3 | const date = new Date(); 4 | const formatter = new Intl.DateTimeFormat('en', { 5 | day: 'numeric', 6 | year: 'numeric', 7 | month: 'long', 8 | }); 9 | const releaseDate = formatter.format(date); 10 | 11 | module.exports = (name = null) => 12 | `${` 13 | /** 14 | * Atropos ${name ? `${name} ` : ''}${pkg.version} 15 | * ${pkg.description} 16 | * ${pkg.homepage} 17 | * 18 | * Copyright 2021-${date.getFullYear()} ${pkg.author} 19 | * 20 | * Released under the ${pkg.license} License 21 | * 22 | * Released on: ${releaseDate} 23 | */ 24 | `.trim()}\n`; 25 | -------------------------------------------------------------------------------- /scripts/build-element.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const less = require('less'); 3 | const CleanCSS = require('clean-css'); 4 | const { rollup } = require('rollup'); 5 | const Terser = require('terser'); 6 | const path = require('path'); 7 | const replace = require('@rollup/plugin-replace'); 8 | 9 | const banner = require('./banner')(); 10 | 11 | async function buildElement(format) { 12 | const env = process.env.NODE_ENV || 'development'; 13 | const outputDir = env === 'development' ? 'build' : 'package'; 14 | const filename = 'atropos-element'; 15 | const ext = format === 'esm' ? 'mjs' : 'js'; 16 | try { 17 | const data = fs.readFileSync('src/atropos.less', 'utf-8'); 18 | let minifiedCss; 19 | 20 | try { 21 | const output = await less.render(data); 22 | const { css } = output; 23 | minifiedCss = new CleanCSS().minify(css).styles; 24 | } catch (err) { 25 | console.error('Error compiling LESS:', err); 26 | return; 27 | } 28 | 29 | const content = fs.readFileSync('src/element/atropos-element.js', 'utf-8'); 30 | const updateContent = content.replace( 31 | "import styles from '../atropos.less';", 32 | `const styles = \`${minifiedCss}\`;`, 33 | ); 34 | 35 | try { 36 | fs.writeFileSync('src/element/atropos-element-tmp.js', updateContent, 'utf-8'); 37 | } catch (err) { 38 | console.error('Error writing file:', err); 39 | return; 40 | } 41 | 42 | const bundle = await rollup({ 43 | input: './src/element/atropos-element-tmp.js', 44 | plugins: [ 45 | replace({ 46 | delimiters: ['', ''], 47 | 'process.env.FORMAT': JSON.stringify(format), 48 | ...(format === 'esm' 49 | ? { 50 | "customElements.define('atropos-component', AtroposComponent);": 51 | 'export default AtroposComponent;', 52 | } 53 | : {}), 54 | }), 55 | ], 56 | }); 57 | const outputFile = path.join(`./${outputDir}/`, `${filename}.${ext}`); 58 | 59 | await bundle.write({ 60 | file: outputFile, 61 | format, 62 | name: 'Atropos Component', 63 | banner, 64 | }); 65 | const code = fs.readFileSync(outputFile, 'utf-8'); 66 | const updateFile = 'src/element/atropos-element-tmp.js'; 67 | fs.removeSync(updateFile); 68 | 69 | if (env === 'development') { 70 | return; 71 | } 72 | // Minify with Terser 73 | // eslint-disable-next-line consistent-return 74 | try { 75 | const minifiedCode = await Terser.minify(code, { 76 | output: { 77 | preamble: banner, 78 | }, 79 | }); 80 | const minifiedOutputFile = path.join(`./${outputDir}/`, `${filename}.min.${ext}`); 81 | fs.writeFileSync(minifiedOutputFile, minifiedCode.code); 82 | } catch (err) { 83 | console.error(`Terser failed: ${err.toString()}`); 84 | } 85 | } catch (err) { 86 | console.error('Error:', err); 87 | } 88 | } 89 | 90 | async function build() { 91 | await Promise.all([buildElement('esm'), buildElement('umd')]); 92 | console.log('Element build completed!'); 93 | } 94 | 95 | module.exports = build; 96 | -------------------------------------------------------------------------------- /scripts/build-js.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | /* eslint no-console: "off" */ 3 | 4 | const fs = require('fs-extra'); 5 | const { rollup } = require('rollup'); 6 | const { default: babel } = require('@rollup/plugin-babel'); 7 | const { default: resolve } = require('@rollup/plugin-node-resolve'); 8 | const replace = require('@rollup/plugin-replace'); 9 | const Terser = require('terser'); 10 | 11 | const banner = require('./banner')(); 12 | 13 | async function buildJs(format) { 14 | const env = process.env.NODE_ENV || 'development'; 15 | const filename = 'atropos'; 16 | const ext = format === 'esm' ? 'mjs' : 'js'; 17 | const output = env === 'development' ? 'build' : 'package'; 18 | const needSourceMap = env === 'production' && (format === 'iife' || format === 'esm'); 19 | 20 | return rollup({ 21 | input: './src/atropos.js', 22 | plugins: [ 23 | replace({ 24 | delimiters: ['', ''], 25 | 'process.env.FORMAT': JSON.stringify(format), 26 | ...(format === 'iife' 27 | ? { 28 | 'export const defaults': 'const defaults', 29 | 'export { Atropos };': '', 30 | } 31 | : {}), 32 | }), 33 | resolve({ mainFields: ['module', 'main', 'jsnext'] }), 34 | babel({ babelHelpers: 'bundled' }), 35 | ], 36 | }) 37 | .then((bundle) => 38 | bundle.write({ 39 | format, 40 | name: 'Atropos', 41 | strict: true, 42 | sourcemap: needSourceMap, 43 | sourcemapFile: `./${output}/${filename}.${ext}.map`, 44 | banner, 45 | file: `./${output}/${filename}.${ext}`, 46 | }), 47 | ) 48 | .then(async (bundle) => { 49 | if (env === 'development') { 50 | return; 51 | } 52 | const result = bundle.output[0]; 53 | const { code, map } = await Terser.minify(result.code, { 54 | sourceMap: { 55 | content: needSourceMap ? result.map : undefined, 56 | filename: needSourceMap ? `${filename}.min.${ext}` : undefined, 57 | url: `${filename}.min.${ext}.map`, 58 | }, 59 | output: { 60 | preamble: banner, 61 | }, 62 | }).catch((err) => { 63 | console.error(`Terser failed on file ${filename}: ${err.toString()}`); 64 | }); 65 | 66 | fs.writeFileSync(`./${output}/${filename}.min.${ext}`, code); 67 | fs.writeFileSync(`./${output}/${filename}.min.${ext}.map`, map); 68 | }) 69 | .catch((err) => { 70 | console.error(err.toString()); 71 | }); 72 | } 73 | 74 | async function build() { 75 | await Promise.all([buildJs('esm'), buildJs('esm'), buildJs('iife')]); 76 | console.log('Scripts build completed!'); 77 | } 78 | 79 | module.exports = build; 80 | -------------------------------------------------------------------------------- /scripts/build-react.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | /* eslint no-console: "off" */ 3 | const { promise: exec } = require('exec-sh'); 4 | const fs = require('fs-extra'); 5 | const bannerReact = require('./banner')('React'); 6 | 7 | module.exports = async () => { 8 | const env = process.env.NODE_ENV || 'development'; 9 | const outputDir = env === 'development' ? 'build' : 'package'; 10 | await exec( 11 | `cross-env MODULES=esm npx babel --config-file ./babel.config.react.js src/react/atropos-react.js --out-file ${outputDir}/atropos-react.mjs`, 12 | ); 13 | 14 | // Add banner 15 | let fileContent = await fs.readFile(`./${outputDir}/atropos-react.mjs`, 'utf-8'); 16 | fileContent = `${bannerReact}\n${fileContent}`; 17 | await fs.writeFile(`./${outputDir}/atropos-react.mjs`, fileContent); 18 | 19 | console.log('React build completed!'); 20 | }; 21 | -------------------------------------------------------------------------------- /scripts/build-styles.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | /* eslint no-console: "off" */ 3 | 4 | const fs = require('fs-extra'); 5 | const path = require('path'); 6 | const less = require('./utils/less'); 7 | const autoprefixer = require('./utils/autoprefixer'); 8 | const minifyCSS = require('./utils/clean-css'); 9 | const banner = require('./banner')(); 10 | 11 | const buildCSS = async ({ minified, outputDir }) => { 12 | const lessContent = await fs.readFile(path.resolve(__dirname, '../src/atropos.less'), 'utf8'); 13 | 14 | const cssContent = await autoprefixer( 15 | await less(lessContent, path.resolve(__dirname, '../src')), 16 | ).catch((err) => { 17 | throw err; 18 | }); 19 | 20 | const fileName = 'atropos'; 21 | 22 | // Write file 23 | await fs.ensureDir(`./${outputDir}`); 24 | await fs.writeFile(`./${outputDir}/${fileName}.css`, `${banner}\n${cssContent}`); 25 | if (minified) { 26 | const minifiedContent = await minifyCSS(cssContent); 27 | await fs.writeFile(`./${outputDir}/${fileName}.min.css`, `${banner}\n${minifiedContent}`); 28 | } 29 | }; 30 | 31 | module.exports = async () => { 32 | const env = process.env.NODE_ENV || 'development'; 33 | const outputDir = env === 'development' ? 'build' : 'package'; 34 | buildCSS({ minified: env !== 'development', outputDir }); 35 | buildCSS({ minified: env !== 'development', outputDir }); 36 | 37 | // Copy less & scss 38 | const files = ['atropos.less', 'atropos.scss']; 39 | await Promise.all( 40 | files.map(async (file) => { 41 | const distFilePath = path.resolve(__dirname, `../${outputDir}`, file); 42 | const srcFilePath = path.resolve(__dirname, '../src', file); 43 | const distFileContent = await fs.readFile(srcFilePath, 'utf-8'); 44 | await fs.ensureDir(path.dirname(distFilePath)); 45 | await fs.writeFile(distFilePath, distFileContent); 46 | }), 47 | ); 48 | 49 | console.log('Styles build completed!'); 50 | }; 51 | -------------------------------------------------------------------------------- /scripts/build-types.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 3 | /* eslint no-console: "off" */ 4 | const path = require('path'); 5 | const fs = require('fs'); 6 | 7 | async function build(cb) { 8 | const env = process.env.NODE_ENV || 'development'; 9 | const outputDir = env === 'development' ? 'build' : 'package'; 10 | 11 | // core 12 | const coreContent = fs.readFileSync(path.resolve(__dirname, '../src/atropos.d.ts'), 'utf-8'); 13 | fs.writeFileSync(path.resolve(__dirname, `../${outputDir}/atropos.d.ts`), coreContent); 14 | 15 | // react 16 | const reactContent = fs.readFileSync( 17 | path.resolve(__dirname, '../src/react/atropos-react.d.ts'), 18 | 'utf-8', 19 | ); 20 | fs.writeFileSync(path.resolve(__dirname, `../${outputDir}/atropos-react.d.ts`), reactContent); 21 | 22 | // element 23 | const elementContent = fs.readFileSync( 24 | path.resolve(__dirname, '../src/element/atropos-element.d.ts'), 25 | 'utf-8', 26 | ); 27 | fs.writeFileSync(path.resolve(__dirname, `../${outputDir}/atropos-element.d.ts`), elementContent); 28 | 29 | console.log('Types build completed!'); 30 | } 31 | 32 | module.exports = build; 33 | -------------------------------------------------------------------------------- /scripts/build.js: -------------------------------------------------------------------------------- 1 | const buildJs = require('./build-js'); 2 | const buildStyles = require('./build-styles'); 3 | const buildTypes = require('./build-types'); 4 | const buildReact = require('./build-react'); 5 | const buildElement = require('./build-element'); 6 | 7 | const build = () => { 8 | buildJs(); 9 | buildStyles(); 10 | buildTypes(); 11 | buildReact(); 12 | buildElement(); 13 | }; 14 | build(); 15 | -------------------------------------------------------------------------------- /scripts/release.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const exec = require('exec-sh'); 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const pkg = require('../package.json'); 6 | const childPkg = require('../package/package.json'); 7 | 8 | async function release() { 9 | // eslint-disable-next-line 10 | const { default: inquirer } = await import('inquirer'); 11 | const date = new Date(); 12 | const formatter = new Intl.DateTimeFormat('en', { 13 | day: 'numeric', 14 | year: 'numeric', 15 | month: 'long', 16 | }); 17 | const releaseDate = formatter.format(date); 18 | 19 | const options = await inquirer.prompt([ 20 | { 21 | type: 'input', 22 | name: 'version', 23 | message: 'Version:', 24 | default: pkg.version, 25 | }, 26 | { 27 | type: 'list', 28 | name: 'alpha', 29 | message: 'Alpha?', 30 | when: (opts) => opts.version.indexOf('alpha') >= 0, 31 | choices: [ 32 | { 33 | name: 'YES', 34 | value: true, 35 | }, 36 | { 37 | name: 'NO', 38 | value: false, 39 | }, 40 | ], 41 | }, 42 | { 43 | type: 'list', 44 | name: 'beta', 45 | message: 'Beta?', 46 | when: (opts) => opts.version.indexOf('beta') >= 0, 47 | choices: [ 48 | { 49 | name: 'YES', 50 | value: true, 51 | }, 52 | { 53 | name: 'NO', 54 | value: false, 55 | }, 56 | ], 57 | }, 58 | { 59 | type: 'list', 60 | name: 'next', 61 | message: 'Next?', 62 | when: (opts) => opts.version.indexOf('next') >= 0, 63 | choices: [ 64 | { 65 | name: 'YES', 66 | value: true, 67 | }, 68 | { 69 | name: 'NO', 70 | value: false, 71 | }, 72 | ], 73 | }, 74 | ]); 75 | // Set version 76 | pkg.version = options.version; 77 | childPkg.version = options.version; 78 | childPkg.releaseDate = releaseDate; 79 | 80 | fs.writeFileSync(path.resolve(__dirname, '../package.json'), `${JSON.stringify(pkg, null, 2)}\n`); 81 | fs.writeFileSync( 82 | path.resolve(__dirname, '../package/package.json'), 83 | `${JSON.stringify(childPkg, null, 2)}\n`, 84 | ); 85 | 86 | const cleanPackage = [ 87 | "find **/*.js -type f -not -name 'postinstall.js' -print0 | xargs -0 -I {} rm -v {}", 88 | 'rm -rf **/*.ts', 89 | 'rm -rf *.ts', 90 | 'rm -rf **/*.css', 91 | 'rm -rf *.css', 92 | 'rm -rf **/*.map', 93 | 'rm -rf *.map', 94 | 'rm -rf **/*.less', 95 | 'rm -rf *.less', 96 | 'rm -rf **/*.scss', 97 | 'rm -rf *.scss', 98 | ]; 99 | 100 | // await exec.promise('git pull'); 101 | await exec.promise('npm i'); 102 | await exec.promise(`cd ./package && ${cleanPackage.join(' && ')}`); 103 | await exec.promise(`npm run build:prod`); 104 | await exec.promise('git add .'); 105 | await exec.promise(`git commit -m "${pkg.version} release"`); 106 | await exec.promise('git push'); 107 | await exec.promise(`git tag v${pkg.version}`); 108 | await exec.promise('git push origin --tags'); 109 | if (options.beta) { 110 | await exec.promise('cd ./package && npm publish --tag beta'); 111 | } else if (options.alpha || options.next) { 112 | await exec.promise('cd ./package && npm publish --tag next'); 113 | } else { 114 | await exec.promise('cd ./package && npm publish'); 115 | } 116 | } 117 | 118 | release(); 119 | -------------------------------------------------------------------------------- /scripts/utils/autoprefixer.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | /* eslint no-console: "off" */ 3 | const postcss = require('postcss'); 4 | const autoprefixer = require('autoprefixer'); 5 | 6 | module.exports = async (content, { from = undefined, to = undefined } = {}) => 7 | new Promise((resolve, reject) => { 8 | postcss([autoprefixer]) 9 | .process(content, { from, to }) 10 | .then((result) => { 11 | result.warnings().forEach((warn) => { 12 | console.warn(warn.toString()); 13 | }); 14 | resolve(result.css); 15 | }) 16 | .catch((err) => { 17 | reject(err); 18 | throw err; 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /scripts/utils/clean-css.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | const CleanCSS = require('clean-css'); 3 | 4 | module.exports = (content, options = {}) => { 5 | // eslint-disable-next-line 6 | options = Object.assign( 7 | { 8 | compatibility: '*,-properties.zeroUnits', 9 | }, 10 | options, 11 | ); 12 | 13 | return new Promise((resolve, reject) => { 14 | if (content instanceof Promise) { 15 | content 16 | .then((c) => { 17 | const minified = new CleanCSS(options).minify(c); 18 | resolve(minified.styles); 19 | }) 20 | .catch((err) => { 21 | reject(err); 22 | throw err; 23 | }); 24 | return; 25 | } 26 | const minified = new CleanCSS(options).minify(content); 27 | resolve(minified.styles); 28 | }); 29 | }; 30 | -------------------------------------------------------------------------------- /scripts/utils/fs-extra.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const path = require('path'); 3 | 4 | const fsExtra = { 5 | unlinkSync(p) { 6 | return fs.unlinkSync(p); 7 | }, 8 | existsSync(p) { 9 | return fs.existsSync(p); 10 | }, 11 | readdirSync(dir) { 12 | return fs.readdirSync(dir); 13 | }, 14 | mkdirSync(dir) { 15 | return fs.mkdirSync(dir); 16 | }, 17 | readFileSync(file) { 18 | return fs.readFileSync(file, 'utf8'); 19 | }, 20 | writeFileSync(file, content) { 21 | fs.ensureDirSync(path.dirname(file)); 22 | return fs.writeFileSync(file, content); 23 | }, 24 | async writeFile(file, content) { 25 | await fs.ensureDir(path.dirname(file)); 26 | return fs.writeFile(file, content); 27 | }, 28 | }; 29 | 30 | module.exports = fsExtra; 31 | -------------------------------------------------------------------------------- /scripts/utils/less.js: -------------------------------------------------------------------------------- 1 | /* eslint import/no-extraneous-dependencies: ["error", {"devDependencies": true}] */ 2 | const less = require('less'); 3 | const path = require('path'); 4 | 5 | module.exports = (content, resolvePath = path.resolve(__dirname, '../../src/core')) => 6 | new Promise((resolve, reject) => { 7 | less 8 | .render(content, { paths: [resolvePath] }) 9 | .then((result) => { 10 | resolve(result.css); 11 | }) 12 | .catch((err) => { 13 | reject(err); 14 | throw err; 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /scripts/watch.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const path = require('path'); 3 | const chalk = require('chalk'); 4 | const buildJs = require('./build-js'); 5 | // const buildTypes = require('./build-types'); 6 | const buildStyles = require('./build-styles'); 7 | const buildReact = require('./build-react'); 8 | const buildElement = require('./build-element'); 9 | 10 | console.log(chalk.cyan('Watching file changes ...')); 11 | 12 | const watchFunction = async (fileName) => { 13 | if (fileName.includes('.less') || fileName.includes('.css') || fileName.includes('.scss')) { 14 | console.log('Building styles'); 15 | await buildStyles(); 16 | return; 17 | } 18 | // if (fileName.includes('.d.ts')) { 19 | // console.log('Building Types'); 20 | // await buildTypes(); 21 | // return; 22 | // } 23 | if (fileName.includes('tmp') || fileName === 'element') { 24 | return; 25 | } 26 | if (fileName.includes('react')) { 27 | console.log('Building React'); 28 | buildReact(); 29 | return; 30 | } 31 | if (fileName.includes('element')) { 32 | console.log({ fileName }); 33 | console.log('Building Element'); 34 | buildElement(); 35 | return; 36 | } 37 | if (fileName.includes('.js')) { 38 | console.log('Building scripts'); 39 | await buildJs(); 40 | return; 41 | } 42 | 43 | console.log('something wrong...'); 44 | }; 45 | 46 | const recursive = !( 47 | process.platform === 'linux' && parseInt(process.versions.node.split('.')[0], 10) >= 14 48 | ); 49 | let watchTimeout; 50 | fs.watch(path.resolve(__dirname, '../src'), { recursive }, (eventType, fileName) => { 51 | clearTimeout(watchTimeout); 52 | watchTimeout = setTimeout(() => { 53 | watchFunction(fileName); 54 | }, 100); 55 | }); 56 | -------------------------------------------------------------------------------- /src/atropos.d.ts: -------------------------------------------------------------------------------- 1 | interface CSSSelector extends String {} 2 | 3 | export interface AtroposOptions { 4 | el?: HTMLElement | CSSSelector; 5 | eventsEl?: HTMLElement | CSSSelector; 6 | alwaysActive?: boolean; 7 | activeOffset?: number; 8 | shadowOffset?: number; 9 | shadowScale?: number; 10 | duration?: number; 11 | rotate?: boolean; 12 | rotateTouch?: boolean | 'scroll-x' | 'scroll-y'; 13 | rotateXMax?: number; 14 | rotateYMax?: number; 15 | rotateXInvert?: boolean; 16 | rotateYInvert?: boolean; 17 | stretchX?: number; 18 | stretchY?: number; 19 | stretchZ?: number; 20 | commonOrigin?: boolean; 21 | shadow?: boolean; 22 | highlight?: boolean; 23 | onEnter?: () => void; 24 | onLeave?: () => void; 25 | onRotate?: (x: number, y: number) => void; 26 | } 27 | 28 | export interface AtroposInstance { 29 | el: HTMLElement; 30 | isActive: boolean; 31 | destroyed: boolean; 32 | params: AtroposOptions; 33 | destroy: () => void; 34 | } 35 | 36 | declare const Atropos: (options: AtroposOptions) => AtroposInstance; 37 | 38 | export default Atropos; 39 | export { Atropos }; 40 | -------------------------------------------------------------------------------- /src/atropos.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | const $ = (el, sel) => el.querySelector(sel); 3 | const $$ = (el, sel) => el.querySelectorAll(sel); 4 | 5 | const removeUndefinedProps = (obj = {}) => { 6 | const result = {}; 7 | Object.keys(obj).forEach((key) => { 8 | if (typeof obj[key] !== 'undefined') result[key] = obj[key]; 9 | }); 10 | return result; 11 | }; 12 | export const defaults = { 13 | alwaysActive: false, 14 | activeOffset: 50, 15 | shadowOffset: 50, 16 | shadowScale: 1, 17 | duration: 300, 18 | rotate: true, 19 | rotateTouch: true, 20 | rotateXMax: 15, 21 | rotateYMax: 15, 22 | rotateXInvert: false, 23 | rotateYInvert: false, 24 | stretchX: 0, 25 | stretchY: 0, 26 | stretchZ: 0, 27 | commonOrigin: true, 28 | shadow: true, 29 | highlight: true, 30 | }; 31 | function Atropos(originalParams = {}) { 32 | let { el, eventsEl } = originalParams; 33 | const { isComponent } = originalParams; 34 | let childrenRootEl; 35 | const self = { 36 | __atropos__: true, 37 | params: { 38 | ...defaults, 39 | onEnter: null, 40 | onLeave: null, 41 | onRotate: null, 42 | ...removeUndefinedProps(originalParams || {}), 43 | }, 44 | destroyed: false, 45 | isActive: false, 46 | }; 47 | 48 | const { params } = self; 49 | 50 | let rotateEl; 51 | let scaleEl; 52 | let innerEl; 53 | 54 | let elBoundingClientRect; 55 | let eventsElBoundingClientRect; 56 | 57 | let shadowEl; 58 | let highlightEl; 59 | 60 | let isScrolling; 61 | let clientXStart; 62 | let clientYStart; 63 | 64 | const queue = []; 65 | let queueFrameId; 66 | const purgeQueue = () => { 67 | queueFrameId = requestAnimationFrame(() => { 68 | queue.forEach((data) => { 69 | if (typeof data === 'function') { 70 | data(); 71 | } else { 72 | const { element, prop, value } = data; 73 | element.style[prop] = value; 74 | } 75 | }); 76 | queue.splice(0, queue.length); 77 | purgeQueue(); 78 | }); 79 | }; 80 | purgeQueue(); 81 | 82 | const $setDuration = (element, value) => { 83 | queue.push({ element, prop: 'transitionDuration', value }); 84 | }; 85 | const $setEasing = (element, value) => { 86 | queue.push({ element, prop: 'transitionTimingFunction', value }); 87 | }; 88 | const $setTransform = (element, value) => { 89 | queue.push({ element, prop: 'transform', value }); 90 | }; 91 | const $setOpacity = (element, value) => { 92 | queue.push({ element, prop: 'opacity', value }); 93 | }; 94 | const $setOrigin = (element, value) => { 95 | queue.push({ element, prop: 'transformOrigin', value }); 96 | }; 97 | const $on = (element, event, handler, props) => element.addEventListener(event, handler, props); 98 | const $off = (element, event, handler, props) => 99 | element.removeEventListener(event, handler, props); 100 | 101 | const createShadow = () => { 102 | let created; 103 | shadowEl = $(el, '.atropos-shadow'); 104 | if (!shadowEl) { 105 | shadowEl = document.createElement('span'); 106 | shadowEl.classList.add('atropos-shadow'); 107 | created = true; 108 | } 109 | $setTransform( 110 | shadowEl, 111 | `translate3d(0,0,-${params.shadowOffset}px) scale(${params.shadowScale})`, 112 | ); 113 | if (created) { 114 | rotateEl.appendChild(shadowEl); 115 | } 116 | }; 117 | const createHighlight = () => { 118 | let created; 119 | highlightEl = $(el, '.atropos-highlight'); 120 | if (!highlightEl) { 121 | highlightEl = document.createElement('span'); 122 | highlightEl.classList.add('atropos-highlight'); 123 | created = true; 124 | } 125 | 126 | $setTransform(highlightEl, `translate3d(0,0,0)`); 127 | if (created) { 128 | innerEl.appendChild(highlightEl); 129 | } 130 | }; 131 | 132 | const setChildrenOffset = ({ 133 | rotateXPercentage = 0, 134 | rotateYPercentage = 0, 135 | duration, 136 | opacityOnly, 137 | easeOut, 138 | }) => { 139 | const getOpacity = (element) => { 140 | if (element.dataset.atroposOpacity && typeof element.dataset.atroposOpacity === 'string') { 141 | return element.dataset.atroposOpacity.split(';').map((v) => parseFloat(v)); 142 | } 143 | return undefined; 144 | }; 145 | 146 | $$(childrenRootEl, '[data-atropos-offset], [data-atropos-opacity]').forEach((childEl) => { 147 | $setDuration(childEl, duration); 148 | $setEasing(childEl, easeOut ? 'ease-out' : ''); 149 | const elementOpacity = getOpacity(childEl); 150 | if (rotateXPercentage === 0 && rotateYPercentage === 0) { 151 | if (!opacityOnly) $setTransform(childEl, `translate3d(0, 0, 0)`); 152 | if (elementOpacity) $setOpacity(childEl, elementOpacity[0]); 153 | } else { 154 | const childElOffset = parseFloat(childEl.dataset.atroposOffset) / 100; 155 | if (!Number.isNaN(childElOffset) && !opacityOnly) { 156 | $setTransform( 157 | childEl, 158 | `translate3d(${-rotateYPercentage * -childElOffset}%, ${ 159 | rotateXPercentage * -childElOffset 160 | }%, 0)`, 161 | ); 162 | } 163 | if (elementOpacity) { 164 | const [min, max] = elementOpacity; 165 | const rotatePercentage = Math.max( 166 | Math.abs(rotateXPercentage), 167 | Math.abs(rotateYPercentage), 168 | ); 169 | $setOpacity(childEl, min + ((max - min) * rotatePercentage) / 100); 170 | } 171 | } 172 | }); 173 | }; 174 | 175 | const setElements = (clientX, clientY) => { 176 | const isMultiple = el !== eventsEl; 177 | if (!elBoundingClientRect) { 178 | elBoundingClientRect = el.getBoundingClientRect(); 179 | } 180 | if (isMultiple && !eventsElBoundingClientRect) { 181 | eventsElBoundingClientRect = eventsEl.getBoundingClientRect(); 182 | } 183 | if (typeof clientX === 'undefined' && typeof clientY === 'undefined') { 184 | const rect = isMultiple ? eventsElBoundingClientRect : elBoundingClientRect; 185 | clientX = rect.left + rect.width / 2; 186 | clientY = rect.top + rect.height / 2; 187 | } 188 | 189 | let rotateX = 0; 190 | let rotateY = 0; 191 | const { top, left, width, height } = elBoundingClientRect; 192 | let transformOrigin; 193 | if (!isMultiple) { 194 | const centerX = width / 2; 195 | const centerY = height / 2; 196 | 197 | const coordX = clientX - left; 198 | const coordY = clientY - top; 199 | 200 | rotateY = ((params.rotateYMax * (coordX - centerX)) / (width / 2)) * -1; 201 | rotateX = (params.rotateXMax * (coordY - centerY)) / (height / 2); 202 | } else { 203 | const { 204 | top: parentTop, 205 | left: parentLeft, 206 | width: parentWidth, 207 | height: parentHeight, 208 | } = eventsElBoundingClientRect; 209 | const offsetLeft = left - parentLeft; 210 | const offsetTop = top - parentTop; 211 | 212 | const centerX = width / 2 + offsetLeft; 213 | const centerY = height / 2 + offsetTop; 214 | 215 | const coordX = clientX - parentLeft; 216 | const coordY = clientY - parentTop; 217 | 218 | rotateY = ((params.rotateYMax * (coordX - centerX)) / (parentWidth - width / 2)) * -1; 219 | rotateX = (params.rotateXMax * (coordY - centerY)) / (parentHeight - height / 2); 220 | transformOrigin = `${clientX - left}px ${clientY - top}px`; 221 | } 222 | 223 | rotateX = Math.min(Math.max(-rotateX, -params.rotateXMax), params.rotateXMax); 224 | if (params.rotateXInvert) rotateX = -rotateX; 225 | rotateY = Math.min(Math.max(-rotateY, -params.rotateYMax), params.rotateYMax); 226 | if (params.rotateYInvert) rotateY = -rotateY; 227 | 228 | const rotateXPercentage = (rotateX / params.rotateXMax) * 100; 229 | const rotateYPercentage = (rotateY / params.rotateYMax) * 100; 230 | 231 | const stretchX = 232 | (isMultiple ? (rotateYPercentage / 100) * params.stretchX : 0) * 233 | (params.rotateYInvert ? -1 : 1); 234 | const stretchY = 235 | (isMultiple ? (rotateXPercentage / 100) * params.stretchY : 0) * 236 | (params.rotateXInvert ? -1 : 1); 237 | const stretchZ = isMultiple 238 | ? (Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100) * params.stretchZ 239 | : 0; 240 | $setTransform( 241 | rotateEl, 242 | `translate3d(${stretchX}%, ${-stretchY}%, ${-stretchZ}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg)`, 243 | ); 244 | if (transformOrigin && params.commonOrigin) { 245 | $setOrigin(rotateEl, transformOrigin); 246 | } 247 | 248 | if (highlightEl) { 249 | $setDuration(highlightEl, `${params.duration}ms`); 250 | $setEasing(highlightEl, 'ease-out'); 251 | $setTransform( 252 | highlightEl, 253 | `translate3d(${-rotateYPercentage * 0.25}%, ${rotateXPercentage * 0.25}%, 0)`, 254 | ); 255 | $setOpacity( 256 | highlightEl, 257 | Math.max(Math.abs(rotateXPercentage), Math.abs(rotateYPercentage)) / 100, 258 | ); 259 | } 260 | 261 | setChildrenOffset({ 262 | rotateXPercentage, 263 | rotateYPercentage, 264 | duration: `${params.duration}ms`, 265 | easeOut: true, 266 | }); 267 | 268 | if (typeof params.onRotate === 'function') params.onRotate(rotateX, rotateY); 269 | }; 270 | 271 | const activate = () => { 272 | queue.push(() => el.classList.add('atropos-active')); 273 | $setDuration(rotateEl, `${params.duration}ms`); 274 | $setEasing(rotateEl, 'ease-out'); 275 | $setTransform(scaleEl, `translate3d(0,0, ${params.activeOffset}px)`); 276 | $setDuration(scaleEl, `${params.duration}ms`); 277 | $setEasing(scaleEl, 'ease-out'); 278 | if (shadowEl) { 279 | $setDuration(shadowEl, `${params.duration}ms`); 280 | $setEasing(shadowEl, 'ease-out'); 281 | } 282 | 283 | self.isActive = true; 284 | }; 285 | 286 | const onPointerEnter = (e) => { 287 | isScrolling = undefined; 288 | if (e.type === 'pointerdown' && e.pointerType === 'mouse') return; 289 | if (e.type === 'pointerenter' && e.pointerType !== 'mouse') return; 290 | if (e.type === 'pointerdown') { 291 | e.preventDefault(); 292 | } 293 | clientXStart = e.clientX; 294 | clientYStart = e.clientY; 295 | 296 | if (params.alwaysActive) { 297 | elBoundingClientRect = undefined; 298 | eventsElBoundingClientRect = undefined; 299 | return; 300 | } 301 | activate(); 302 | if (typeof params.onEnter === 'function') params.onEnter(); 303 | }; 304 | 305 | const onTouchMove = (e) => { 306 | if (isScrolling === false && e.cancelable) { 307 | e.preventDefault(); 308 | } 309 | }; 310 | 311 | const onPointerMove = (e) => { 312 | if (!params.rotate || !self.isActive) return; 313 | if (e.pointerType !== 'mouse') { 314 | if (!params.rotateTouch) return; 315 | e.preventDefault(); 316 | } 317 | const { clientX, clientY } = e; 318 | 319 | const diffX = clientX - clientXStart; 320 | const diffY = clientY - clientYStart; 321 | if ( 322 | typeof params.rotateTouch === 'string' && 323 | (diffX !== 0 || diffY !== 0) && 324 | typeof isScrolling === 'undefined' 325 | ) { 326 | if (diffX * diffX + diffY * diffY >= 25) { 327 | const touchAngle = (Math.atan2(Math.abs(diffY), Math.abs(diffX)) * 180) / Math.PI; 328 | isScrolling = params.rotateTouch === 'scroll-y' ? touchAngle > 45 : 90 - touchAngle > 45; 329 | } 330 | if (isScrolling === false) { 331 | el.classList.add('atropos-rotate-touch'); 332 | if (e.cancelable) { 333 | e.preventDefault(); 334 | } 335 | } 336 | } 337 | if (e.pointerType !== 'mouse' && isScrolling) { 338 | return; 339 | } 340 | setElements(clientX, clientY); 341 | }; 342 | 343 | const onPointerLeave = (e) => { 344 | elBoundingClientRect = undefined; 345 | eventsElBoundingClientRect = undefined; 346 | if (!self.isActive) return; 347 | if (e && e.type === 'pointerup' && e.pointerType === 'mouse') return; 348 | if (e && e.type === 'pointerleave' && e.pointerType !== 'mouse') return; 349 | if (typeof params.rotateTouch === 'string' && isScrolling) { 350 | el.classList.remove('atropos-rotate-touch'); 351 | } 352 | 353 | if (params.alwaysActive) { 354 | setElements(); 355 | if (typeof params.onRotate === 'function') params.onRotate(0, 0); 356 | if (typeof params.onLeave === 'function') params.onLeave(); 357 | return; 358 | } 359 | 360 | queue.push(() => el.classList.remove('atropos-active')); 361 | $setDuration(scaleEl, `${params.duration}ms`); 362 | $setEasing(scaleEl, ''); 363 | $setTransform(scaleEl, `translate3d(0,0, ${0}px)`); 364 | if (shadowEl) { 365 | $setDuration(shadowEl, `${params.duration}ms`); 366 | $setEasing(shadowEl, ''); 367 | } 368 | if (highlightEl) { 369 | $setDuration(highlightEl, `${params.duration}ms`); 370 | $setEasing(highlightEl, ''); 371 | $setTransform(highlightEl, `translate3d(0, 0, 0)`); 372 | $setOpacity(highlightEl, 0); 373 | } 374 | $setDuration(rotateEl, `${params.duration}ms`); 375 | $setEasing(rotateEl, ''); 376 | $setTransform(rotateEl, `translate3d(0,0,0) rotateX(0deg) rotateY(0deg)`); 377 | 378 | setChildrenOffset({ duration: `${params.duration}ms` }); 379 | 380 | self.isActive = false; 381 | if (typeof params.onRotate === 'function') params.onRotate(0, 0); 382 | if (typeof params.onLeave === 'function') params.onLeave(); 383 | }; 384 | 385 | const onDocumentClick = (e) => { 386 | const clickTarget = e.target; 387 | if (!eventsEl.contains(clickTarget) && clickTarget !== eventsEl && self.isActive) { 388 | onPointerLeave(); 389 | } 390 | }; 391 | 392 | const initDOM = () => { 393 | if (typeof el === 'string') { 394 | el = $(document, el); 395 | } 396 | if (!el) return; 397 | 398 | // eslint-disable-next-line 399 | if (el.__atropos__) return; 400 | 401 | if (typeof eventsEl !== 'undefined') { 402 | if (typeof eventsEl === 'string') { 403 | eventsEl = $(document, eventsEl); 404 | } 405 | } else { 406 | eventsEl = el; 407 | } 408 | childrenRootEl = isComponent ? el.parentNode.host : el; 409 | 410 | Object.assign(self, { 411 | el, 412 | }); 413 | 414 | rotateEl = $(el, '.atropos-rotate'); 415 | scaleEl = $(el, '.atropos-scale'); 416 | innerEl = $(el, '.atropos-inner'); 417 | 418 | // eslint-disable-next-line 419 | el.__atropos__ = self; 420 | }; 421 | 422 | const init = () => { 423 | initDOM(); 424 | if (!el || !eventsEl) return; 425 | if (params.shadow) { 426 | createShadow(); 427 | } 428 | if (params.highlight) { 429 | createHighlight(); 430 | } 431 | if (params.rotateTouch) { 432 | if (typeof params.rotateTouch === 'string') { 433 | el.classList.add(`atropos-rotate-touch-${params.rotateTouch}`); 434 | } else { 435 | el.classList.add('atropos-rotate-touch'); 436 | } 437 | } 438 | if ($(childrenRootEl, '[data-atropos-opacity]')) { 439 | setChildrenOffset({ opacityOnly: true }); 440 | } 441 | $on(document, 'click', onDocumentClick); 442 | $on(eventsEl, 'pointerdown', onPointerEnter); 443 | $on(eventsEl, 'pointerenter', onPointerEnter); 444 | $on(eventsEl, 'pointermove', onPointerMove); 445 | $on(eventsEl, 'touchmove', onTouchMove); 446 | $on(eventsEl, 'pointerleave', onPointerLeave); 447 | $on(eventsEl, 'pointerup', onPointerLeave); 448 | $on(eventsEl, 'lostpointercapture', onPointerLeave); 449 | 450 | if (params.alwaysActive) { 451 | activate(); 452 | setElements(); 453 | } 454 | }; 455 | 456 | const destroy = () => { 457 | self.destroyed = true; 458 | cancelAnimationFrame(queueFrameId); 459 | $off(document, 'click', onDocumentClick); 460 | $off(eventsEl, 'pointerdown', onPointerEnter); 461 | $off(eventsEl, 'pointerenter', onPointerEnter); 462 | $off(eventsEl, 'pointermove', onPointerMove); 463 | $off(eventsEl, 'touchmove', onTouchMove); 464 | $off(eventsEl, 'pointerleave', onPointerLeave); 465 | $off(eventsEl, 'pointerup', onPointerLeave); 466 | $off(eventsEl, 'lostpointercapture', onPointerLeave); 467 | // eslint-disable-next-line 468 | delete el.__atropos__; 469 | }; 470 | 471 | self.destroy = destroy; 472 | 473 | init(); 474 | 475 | // eslint-disable-next-line 476 | return self; 477 | } 478 | export { Atropos }; 479 | export default Atropos; 480 | -------------------------------------------------------------------------------- /src/atropos.less: -------------------------------------------------------------------------------- 1 | .atropos { 2 | position: relative; 3 | display: block; 4 | perspective: 1200px; 5 | transform: translate3d(0, 0, 0); 6 | &-rotate-touch, 7 | &-rotate-scroll-x, 8 | &-rotate-scroll-y { 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | -webkit-touch-callout: none; 11 | user-select: none; 12 | } 13 | 14 | &-rotate-touch-scroll-y { 15 | touch-action: pan-y; 16 | } 17 | &-rotate-touch-scroll-x { 18 | touch-action: pan-x; 19 | } 20 | &-rotate-touch { 21 | touch-action: none; 22 | } 23 | } 24 | .atropos-scale, 25 | .atropos-rotate { 26 | width: 100%; 27 | height: 100%; 28 | transform-style: preserve-3d; 29 | transition-property: transform; 30 | display: block; 31 | } 32 | .atropos-shadow, 33 | .atropos-highlight { 34 | position: absolute; 35 | pointer-events: none; 36 | transition-property: transform, opacity; 37 | display: block; 38 | opacity: 0; 39 | } 40 | .atropos-shadow { 41 | z-index: -1; 42 | background: #000; 43 | left: 0; 44 | top: 0; 45 | width: 100%; 46 | height: 100%; 47 | filter: blur(30px); 48 | } 49 | .atropos-highlight { 50 | left: -50%; 51 | top: -50%; 52 | width: 200%; 53 | height: 200%; 54 | background-image: radial-gradient(circle at 50%, rgba(255, 255, 255, 0.25), transparent 50%); 55 | z-index: 0; 56 | } 57 | .atropos-rotate { 58 | position: relative; 59 | } 60 | .atropos-inner { 61 | width: 100%; 62 | height: 100%; 63 | position: relative; 64 | overflow: hidden; 65 | transform-style: preserve-3d; 66 | transform: translate3d(0, 0, 0); 67 | display: block; 68 | } 69 | .atropos-active { 70 | z-index: 1; 71 | .atropos-shadow { 72 | opacity: 1 !important; 73 | } 74 | } 75 | [data-atropos-offset], 76 | ::slotted([data-atropos-offset]) { 77 | transition-property: transform; 78 | } 79 | [data-atropos-opacity] { 80 | transition-property: opacity; 81 | } 82 | [data-atropos-offset][data-atropos-opacity], 83 | ::slotted([data-atropos-offset][data-atropos-opacity]) { 84 | transition-property: transform, opacity; 85 | } 86 | -------------------------------------------------------------------------------- /src/atropos.scss: -------------------------------------------------------------------------------- 1 | .atropos { 2 | position: relative; 3 | display: block; 4 | perspective: 1200px; 5 | transform: translate3d(0, 0, 0); 6 | &-rotate-touch, 7 | &-rotate-scroll-x, 8 | &-rotate-scroll-y { 9 | -webkit-tap-highlight-color: rgba(0, 0, 0, 0); 10 | -webkit-touch-callout: none; 11 | user-select: none; 12 | } 13 | 14 | &-rotate-touch-scroll-y { 15 | touch-action: pan-y; 16 | } 17 | &-rotate-touch-scroll-x { 18 | touch-action: pan-x; 19 | } 20 | &-rotate-touch { 21 | touch-action: none; 22 | } 23 | } 24 | .atropos-scale, 25 | .atropos-rotate { 26 | width: 100%; 27 | height: 100%; 28 | transform-style: preserve-3d; 29 | transition-property: transform; 30 | display: block; 31 | } 32 | .atropos-shadow, 33 | .atropos-highlight { 34 | position: absolute; 35 | pointer-events: none; 36 | transition-property: transform, opacity; 37 | display: block; 38 | opacity: 0; 39 | } 40 | .atropos-shadow { 41 | z-index: -1; 42 | background: #000; 43 | left: 0; 44 | top: 0; 45 | width: 100%; 46 | height: 100%; 47 | filter: blur(30px); 48 | } 49 | .atropos-highlight { 50 | left: -50%; 51 | top: -50%; 52 | width: 200%; 53 | height: 200%; 54 | background-image: radial-gradient(circle at 50%, rgba(255, 255, 255, 0.25), transparent 50%); 55 | z-index: 0; 56 | } 57 | .atropos-rotate { 58 | position: relative; 59 | } 60 | .atropos-inner { 61 | width: 100%; 62 | height: 100%; 63 | position: relative; 64 | overflow: hidden; 65 | transform-style: preserve-3d; 66 | transform: translate3d(0, 0, 0); 67 | display: block; 68 | } 69 | .atropos-active { 70 | z-index: 1; 71 | .atropos-shadow { 72 | opacity: 1 !important; 73 | } 74 | } 75 | [data-atropos-offset] { 76 | transition-property: transform; 77 | } 78 | [data-atropos-opacity] { 79 | transition-property: opacity; 80 | } 81 | [data-atropos-offset][data-atropos-opacity] { 82 | transition-property: transform, opacity; 83 | } 84 | -------------------------------------------------------------------------------- /src/element/atropos-element.d.ts: -------------------------------------------------------------------------------- 1 | // prettier-ignore 2 | import type { AtroposInstance } from './atropos.d.ts'; 3 | 4 | // prettier-ignore 5 | interface AtroposEventMap extends Omit { 6 | enter: CustomEvent; 7 | leave: CustomEvent; 8 | rotate: CustomEvent; 9 | } 10 | interface AtroposComponent extends HTMLElement { 11 | atroposRef?: AtroposInstance; 12 | eventsEl?: HTMLElement | CSSSelector; 13 | alwaysActive?: boolean; 14 | activeOffset?: number; 15 | shadowOffset?: number; 16 | shadowScale?: number; 17 | duration?: number; 18 | rotate?: boolean; 19 | rotateTouch?: boolean | 'scroll-x' | 'scroll-y'; 20 | rotateXMax?: number; 21 | rotateYMax?: number; 22 | rotateXInvert?: boolean; 23 | rotateYInvert?: boolean; 24 | stretchX?: number; 25 | stretchY?: number; 26 | stretchZ?: number; 27 | commonOrigin?: boolean; 28 | shadow?: boolean; 29 | highlight?: boolean; 30 | addEventListener( 31 | type: K, 32 | listener: (this: AtroposComponent, ev: AtroposEventMap[K]) => any, 33 | options?: boolean | AddEventListenerOptions, 34 | ): void; 35 | addEventListener( 36 | type: string, 37 | listener: EventListenerOrEventListenerObject, 38 | options?: boolean | AddEventListenerOptions, 39 | ): void; 40 | removeEventListener( 41 | type: K, 42 | listener: (this: AtroposComponent, ev: AtroposEventMap[K]) => any, 43 | options?: boolean | EventListenerOptions, 44 | ): void; 45 | removeEventListener( 46 | type: string, 47 | listener: EventListenerOrEventListenerObject, 48 | options?: boolean | EventListenerOptions, 49 | ): void; 50 | } 51 | 52 | export default AtroposComponent; 53 | -------------------------------------------------------------------------------- /src/element/atropos-element.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | // eslint-disable-next-line import/no-named-as-default 3 | import Atropos, { defaults } from '../atropos.js'; 4 | import styles from '../atropos.less'; 5 | 6 | class AtroposComponent extends HTMLElement { 7 | // eslint-disable-next-line no-useless-constructor 8 | constructor() { 9 | super(); 10 | this.shadow = this.attachShadow({ mode: 'open' }); 11 | } 12 | 13 | connectedCallback() { 14 | this.init(); 15 | } 16 | 17 | disconnectedCallback() { 18 | this.destroy(); 19 | } 20 | 21 | init() { 22 | const defaultProps = { 23 | ...defaults, 24 | }; 25 | 26 | const props = {}; 27 | 28 | Object.keys(defaultProps).forEach((key) => { 29 | const attributeName = key.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`); 30 | const attributeValue = this.getAttribute(attributeName); 31 | 32 | if (attributeValue === null) { 33 | props[key] = defaultProps[key]; 34 | } else { 35 | switch (typeof defaultProps[key]) { 36 | case 'boolean': 37 | props[key] = attributeValue !== 'false'; 38 | break; 39 | case 'number': 40 | props[key] = isNaN(parseFloat(attributeValue, 10)) 41 | ? defaultProps[key] 42 | : parseFloat(attributeValue, 10); 43 | break; 44 | default: 45 | props[key] = attributeValue; 46 | } 47 | } 48 | }); 49 | const innerClass = this.cls('atropos-inner', props.innerClass); 50 | 51 | // eslint-disable-next-line no-restricted-globals 52 | const el = document.createElement('div'); 53 | el.classList.add('atropos'); 54 | 55 | el.innerHTML = ` 56 |
57 |
58 |
59 | 60 |
61 | 62 |
63 | 64 |
65 | 66 | `; 67 | this.shadow.innerHTML = ''; 68 | 69 | if (typeof CSSStyleSheet !== 'undefined' && this.shadow.adoptedStyleSheets) { 70 | // eslint-disable-next-line no-restricted-globals 71 | const styleSheet = new CSSStyleSheet(); 72 | styleSheet.replaceSync(styles); 73 | this.shadow.adoptedStyleSheets = [styleSheet]; 74 | } else { 75 | const styleEl = document.createElement('style'); 76 | styleEl.rel = 'stylesheet'; 77 | styleEl.textContent = styles; 78 | this.shadow.appendChild(styleEl); 79 | } 80 | 81 | this.shadow.appendChild(el); 82 | 83 | this.atroposRef = Atropos({ 84 | el, 85 | isComponent: true, 86 | ...props, 87 | onEnter: () => { 88 | this.dispatchEvent(new CustomEvent('enter')); 89 | }, 90 | onLeave: () => { 91 | this.dispatchEvent(new CustomEvent('leave')); 92 | }, 93 | onRotate: (...args) => { 94 | this.dispatchEvent(new CustomEvent('rotate', { detail: args })); 95 | }, 96 | }); 97 | } 98 | 99 | destroy() { 100 | if (this.atroposInstance) { 101 | this.atroposInstance.destroy(); 102 | this.atroposInstance = null; 103 | } 104 | } 105 | 106 | // eslint-disable-next-line class-methods-use-this 107 | cls(...args) { 108 | return args.filter((c) => !!c).join(' '); 109 | } 110 | } 111 | customElements.define('atropos-component', AtroposComponent); 112 | -------------------------------------------------------------------------------- /src/react/atropos-react.d.ts: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | // prettier-ignore 4 | import type { AtroposOptions } from './atropos.d.ts'; 5 | 6 | interface Atropos extends AtroposOptions { 7 | component?: string; 8 | rootChildren?: React.ReactNode; 9 | scaleChildren?: React.ReactNode; 10 | rotateChildren?: React.ReactNode; 11 | scaleClassName?: string; 12 | rotateClassName?: string; 13 | innerClassName?: string; 14 | } 15 | 16 | interface Atropos extends React.HTMLAttributes {} 17 | 18 | declare const Atropos: React.FunctionComponent; 19 | 20 | export { Atropos }; 21 | export default Atropos; 22 | -------------------------------------------------------------------------------- /src/react/atropos-react.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react'; 2 | // eslint-disable-next-line 3 | import AtroposCore from './atropos.mjs'; 4 | 5 | const paramsKeys = [ 6 | 'eventsEl', 7 | 'alwaysActive', 8 | 'activeOffset', 9 | 'shadowOffset', 10 | 'shadowScale', 11 | 'duration', 12 | 'rotate', 13 | 'rotateTouch', 14 | 'rotateXMax', 15 | 'rotateYMax', 16 | 'rotateXInvert', 17 | 'rotateYInvert', 18 | 'stretchX', 19 | 'stretchY', 20 | 'stretchZ', 21 | 'commonOrigin', 22 | 'shadow', 23 | 'highlight', 24 | 'onEnter', 25 | 'onLeave', 26 | 'onRotate', 27 | ]; 28 | 29 | const removeParamsKeys = (obj) => { 30 | const result = {}; 31 | Object.keys(obj).forEach((key) => { 32 | if (!paramsKeys.includes(key)) result[key] = obj[key]; 33 | }); 34 | return result; 35 | }; 36 | 37 | const extractParamsKeys = (obj) => { 38 | const result = {}; 39 | Object.keys(obj).forEach((key) => { 40 | if (paramsKeys.includes(key)) result[key] = obj[key]; 41 | }); 42 | return result; 43 | }; 44 | 45 | function Atropos(props) { 46 | const { 47 | component = 'div', 48 | children, 49 | rootChildren, 50 | scaleChildren, 51 | rotateChildren, 52 | className = '', 53 | scaleClassName = '', 54 | rotateClassName = '', 55 | innerClassName = '', 56 | 57 | ...rest 58 | } = props; 59 | 60 | const elRef = useRef(null); 61 | const atroposRef = useRef(null); 62 | 63 | const Component = component; 64 | 65 | const cls = (...args) => { 66 | return args.filter((c) => !!c).join(' '); 67 | }; 68 | 69 | const init = () => { 70 | atroposRef.current = AtroposCore({ 71 | el: elRef.current, 72 | ...extractParamsKeys(props), 73 | }); 74 | }; 75 | 76 | const destroy = () => { 77 | if (atroposRef.current) { 78 | atroposRef.current.destroy(); 79 | atroposRef.current = null; 80 | } 81 | }; 82 | 83 | useEffect(() => { 84 | if (elRef.current) { 85 | init(); 86 | } 87 | 88 | return () => { 89 | destroy(); 90 | }; 91 | }, []); 92 | 93 | useEffect(() => { 94 | if (atroposRef.current) { 95 | atroposRef.current.params.onEnter = props.onEnter; 96 | atroposRef.current.params.onLeave = props.onLeave; 97 | atroposRef.current.params.onRotate = props.onRotate; 98 | } 99 | return () => { 100 | if (atroposRef.current) { 101 | atroposRef.current.params.onEnter = null; 102 | atroposRef.current.params.onLeave = null; 103 | atroposRef.current.params.onRotate = null; 104 | } 105 | }; 106 | }); 107 | 108 | return ( 109 | 110 | 111 | 112 | 113 | {children} 114 | {(props.highlight || typeof props.highlight === 'undefined') && ( 115 | 116 | )} 117 | 118 | {rotateChildren} 119 | {(props.shadow || typeof props.shadow === 'undefined') && ( 120 | 121 | )} 122 | 123 | {scaleChildren} 124 | 125 | {rootChildren} 126 | 127 | ); 128 | } 129 | 130 | export default Atropos; 131 | export { Atropos }; 132 | --------------------------------------------------------------------------------