├── .eslintrc.json ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── custom.md │ └── feature_request.md ├── dependabot.yml └── workflows │ ├── codacy.yml │ └── static.yml ├── .gitignore ├── .npmignore ├── .prettierrc.json ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── SECURITY.md ├── babel.config.json ├── dist ├── Textify.min.css ├── Textify.min.js └── Textify.min.js.LICENSE.txt ├── docs └── main-site │ ├── .eslintrc.cjs │ ├── .gitignore │ ├── .prettierrc.json │ ├── .vscode │ └── extensions.json │ ├── README.md │ ├── index.html │ ├── package-lock.json │ ├── package.json │ ├── public │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ ├── apple-touch-icon.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── favicon.ico │ └── site.webmanifest │ ├── src │ ├── App.vue │ ├── assets │ │ ├── code.svg │ │ ├── copy.svg │ │ ├── default.svg │ │ ├── restart.svg │ │ └── styles │ │ │ ├── base │ │ │ ├── _base.scss │ │ │ ├── _reset.scss │ │ │ └── _typography.scss │ │ │ ├── components │ │ │ ├── _codebox.scss │ │ │ ├── _footer.scss │ │ │ ├── _navigation.scss │ │ │ └── _sidebar.scss │ │ │ ├── index.scss │ │ │ ├── layouts │ │ │ ├── _flexbox.scss │ │ │ └── _scroll.scss │ │ │ ├── pages │ │ │ ├── _docs.scss │ │ │ ├── _example.scss │ │ │ ├── _home.scss │ │ │ ├── _notfound.scss │ │ │ └── _tutorial.scss │ │ │ ├── shared │ │ │ ├── _links.scss │ │ │ ├── _sections.scss │ │ │ └── _texts.scss │ │ │ └── utils │ │ │ └── _variables.scss │ ├── components │ │ ├── AnimationBox.vue │ │ ├── CodeBox.vue │ │ ├── FooterBox.vue │ │ ├── LargeAnimationBox.vue │ │ ├── Navigation.vue │ │ └── SideBar.vue │ ├── layouts │ │ └── MainLayout.vue │ ├── library │ │ ├── Textify.min.css │ │ └── Textify.min.js │ ├── main.js │ ├── router │ │ └── index.js │ └── views │ │ ├── Documentation.vue │ │ ├── Example.vue │ │ ├── Home.vue │ │ ├── NotFound.vue │ │ └── Tutorial.vue │ ├── vercel.json │ └── vite.config.js ├── package-lock.json ├── package.json ├── src ├── Utils │ ├── Break.js │ ├── Config.js │ ├── Text.js │ ├── dom.js │ └── isBrowser.js ├── index.js └── types │ └── Texts.js ├── style └── Textify.css └── webpack.config.js /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "env":{ 4 | "browser":true, 5 | "node":true , 6 | "es6":true 7 | }, 8 | "extends":["eslint:recommended","prettier"], 9 | "parser": "@babel/eslint-parser", 10 | "parserOptions":{ 11 | "sourceType":"module" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/custom.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Custom issue template 3 | about: Describe this issue template's purpose here. 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /.github/workflows/codacy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | 6 | # This workflow checks out code, performs a Codacy security scan 7 | # and integrates the results with the 8 | # GitHub Advanced Security code scanning feature. For more information on 9 | # the Codacy security scan action usage and parameters, see 10 | # https://github.com/codacy/codacy-analysis-cli-action. 11 | # For more information on Codacy Analysis CLI in general, see 12 | # https://github.com/codacy/codacy-analysis-cli. 13 | 14 | name: Codacy Security Scan 15 | 16 | on: 17 | push: 18 | branches: [ "master" ] 19 | pull_request: 20 | # The branches below must be a subset of the branches above 21 | branches: [ "master" ] 22 | schedule: 23 | - cron: '23 13 * * 2' 24 | 25 | permissions: 26 | contents: read 27 | 28 | jobs: 29 | codacy-security-scan: 30 | permissions: 31 | contents: read # for actions/checkout to fetch code 32 | security-events: write # for github/codeql-action/upload-sarif to upload SARIF results 33 | name: Codacy Security Scan 34 | runs-on: ubuntu-latest 35 | steps: 36 | # Checkout the repository to the GitHub Actions runner 37 | - name: Checkout code 38 | uses: actions/checkout@v3 39 | 40 | # Execute Codacy Analysis CLI and generate a SARIF output with the security issues identified during the analysis 41 | - name: Run Codacy Analysis CLI 42 | uses: codacy/codacy-analysis-cli-action@d840f886c4bd4edc059706d09c6a1586111c540b 43 | with: 44 | # Check https://github.com/codacy/codacy-analysis-cli#project-token to get your project token from your Codacy repository 45 | # You can also omit the token and run the tools that support default configurations 46 | project-token: ${{ secrets.CODACY_PROJECT_TOKEN }} 47 | verbose: true 48 | output: results.sarif 49 | format: sarif 50 | # Adjust severity of non-security issues 51 | gh-code-scanning-compat: true 52 | # Force 0 exit code to allow SARIF file generation 53 | # This will handover control about PR rejection to the GitHub side 54 | max-allowed-issues: 2147483647 55 | 56 | # Upload the SARIF file generated in the previous step 57 | - name: Upload SARIF results file 58 | uses: github/codeql-action/upload-sarif@v2 59 | with: 60 | sarif_file: results.sarif 61 | -------------------------------------------------------------------------------- /.github/workflows/static.yml: -------------------------------------------------------------------------------- 1 | # Simple workflow for deploying static content to GitHub Pages 2 | name: Deploy static content to Pages 3 | 4 | on: 5 | # Runs on pushes targeting the default branch 6 | push: 7 | branches: ["master"] 8 | 9 | # Allows you to run this workflow manually from the Actions tab 10 | workflow_dispatch: 11 | 12 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 13 | permissions: 14 | contents: read 15 | pages: write 16 | id-token: write 17 | 18 | # Allow one concurrent deployment 19 | concurrency: 20 | group: "pages" 21 | cancel-in-progress: true 22 | 23 | jobs: 24 | # Single deploy job since we're just deploying 25 | deploy: 26 | environment: 27 | name: github-pages 28 | url: ${{ steps.deployment.outputs.page_url }} 29 | runs-on: ubuntu-latest 30 | steps: 31 | - name: Checkout 32 | uses: actions/checkout@v3 33 | - name: Setup Pages 34 | uses: actions/configure-pages@v3 35 | - name: Upload artifact 36 | uses: actions/upload-pages-artifact@v1 37 | with: 38 | # Upload entire repository 39 | path: '.' 40 | - name: Deploy to GitHub Pages 41 | id: deployment 42 | uses: actions/deploy-pages@v1 43 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | /.npm 4 | /.yarn 5 | /.DS_Store 6 | /.git 7 | /examples 8 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /.cache 3 | /.npm 4 | /.yarn 5 | /.DS_Store 6 | /package-lock.json 7 | /yarn.lock 8 | /webpack.config.js 9 | /examples 10 | /website 11 | /docs 12 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": false, 3 | "printWidth": 150, 4 | "singleQuote": false, 5 | "trailingComma": "none", 6 | "semi": true, 7 | "arrowParens": "always" 8 | } 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | jeetpatel0070070@gmail.com. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Jeet 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 | logo 4 |

5 |

Next Generation Text Animation Library.

6 |

7 | 8 | 9 | 10 | maintained - yes 11 | contributions - welcome 12 | Made with JavaScript 13 |

14 |

15 | 16 | mini-logo 17 |
18 | Live Examples
19 |

20 |
21 |
22 | 23 | ## Getting Started 24 | Textify.js is a animation engine for web typography animations, which are use to create smooth, creative or seamless animations of typography. Also it’s provide multiple 25 | animations types or custom animations on GSAP's power. 26 | 27 | # 28 | ## Using packge manager 29 | 30 | #### NPM 31 | Install textify using npm: 32 | ```sh 33 | npm install textify.js 34 | ``` 35 | 36 | #### yarn 37 | Install textify using yarn 38 | ```sh 39 | yarn add textify.js 40 | ``` 41 | 42 | # 43 | ## Using CDN: 44 | ```html 45 | 46 | 47 | 48 | ``` 49 | 50 | # 51 | ## ES6 module 52 | ```html 53 | 59 | ``` 60 | 61 | # 62 | ## Usage 63 | Import Textify.js and gsap: 64 | ```javascript 65 | import Textify from "textify.js"; 66 | import gsap from "gsap"; 67 | ``` 68 | 69 | Link ```Textify.min.css``` to document: 70 | 71 | ```html 72 | 73 | ``` 74 | 75 | Add `data-textify` attribute to your paragraph or an element that contain text. 76 | ```html 77 |

Some cool text.😎😎

78 | ``` 79 | 80 | Initialize textify to see magic (add gsap too). 81 | ```javascript 82 | import Textify from "textify.js"; 83 | import gsap from "gsap"; 84 | 85 | new Textify({}, gsap); 86 | ``` 87 | 88 |

By default textify use default configurations for text animations. You can pass an configuration object during initialization to tweak it.

89 | 90 | # 91 | ## Configuration options 92 | 93 | ```javascript 94 | splitType: "lines words chars", // chars or words or lines 95 | largeText: false, // true or false 96 | observer: { 97 | repeat: false, // true or false 98 | threshold: 0.5 // 0.0 ~ 1.0 99 | }, 100 | animation: { 101 | by: "chars", // chars or words or lines 102 | duration: 0.5, // seconds 103 | stagger: 0.05, // seconds 104 | delay: 0.0, // seconds 105 | ease: "ease", // ease or linear or cubic-bezier 106 | customAnimation: false, // true or false 107 | transformOrigin: "center center", // center center or top left or top center or top right or center right or bottom right or bottom center or bottom left or center left 108 | animateProps: { 109 | opacity: 1, // 0 ~ 1 110 | y: "100%", // -100 ~ 100 (%) 111 | x: 0, // -100 ~ 100 (%) 112 | scale: 1, // 0 ~ 1 113 | rotate: 0, // -360 ~ 360 114 | skewX: 0, // -360 ~ 360 115 | skewY: 0 // -360 ~ 360 116 | } 117 | ``` 118 | 119 | For, more information about configs, check Documentation 120 | 121 | # 122 | # Documentation 123 | Check main documentation of Textify.js here: 124 | 125 | - [Site](https://textify-js.vercel.app) 126 | - [Documentation & Examples](https://textify-js.vercel.app/documentation) 127 | - [Demo](https://textify-js.vercel.app/example) 128 | 129 | # 130 | # Methods 131 | Textify contains instance methods. these are used to control the animation. these methods are help to maintain animation stability. these 132 | methods are following: 133 | 134 | * `animateIn` - Reveal animation. 135 | * `animateOut` - Hide animation. 136 | * `reset` - Re-calulate all texts position and offset (call on DOM changes and resize event) 137 | 138 | Example: 139 | ```javascript 140 | const textObj = new Textify({}, gsap); 141 | 142 | // reveal all animations of textObj 143 | textObj.animateIn(); 144 | 145 | // hide all animations of textObj 146 | textObj.animateOut(); 147 | 148 | // Re-calulate all texts position and offset 149 | textObj.reset(); 150 | 151 | ``` 152 | 153 | ## License 154 | 155 | Released under [MIT](/LICENSE) by [@MAGGIx1404](https://github.com/MAGGIx1404). 156 | 157 | ## Rate us 158 | 159 | Enjoying textify.js? [Please leave a short review on Openbase](https://openbase.com/js/textify.js#rate) 160 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /babel.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceType": "unambiguous", 3 | "plugins": ["@babel/plugin-transform-runtime"], 4 | "presets": ["@babel/preset-env"] 5 | } 6 | -------------------------------------------------------------------------------- /dist/Textify.min.css: -------------------------------------------------------------------------------- 1 | /* fix safari custom kerning by adding a space after each character */ 2 | * { 3 | -webkit-font-feature-settings: "kern" 1; 4 | -moz-font-feature-settings: "kern" 1; 5 | -ms-font-feature-settings: "kern" 1; 6 | -o-font-feature-settings: "kern" 1; 7 | font-feature-settings: "kern" 1; 8 | font-kerning: none; 9 | -webkit-text-rendering: optimizeSpeed; 10 | text-rendering: optimizeSpeed; 11 | -webkit-transform: translateZ(0); 12 | transform: translateZ(0); 13 | } 14 | 15 | /* ----------------------------------------- */ 16 | .textify .word, 17 | .textify .char { 18 | display: inline-block; 19 | will-change: transform, opacity; /* for safari */ 20 | } 21 | 22 | /* ----------------------------------------- */ 23 | 24 | .textify .char { 25 | position: relative; 26 | } 27 | 28 | /** 29 | * Populate the psuedo elements with the character to allow for expanded effects 30 | * Set to `display: none` by default; just add `display: block` when you want 31 | * to use the psuedo elements 32 | */ 33 | .textify .char::before, 34 | .textify .char::after, 35 | .textify .word::after, 36 | .textify .word::before, 37 | .textify .line::after, 38 | .textify .line::before { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | visibility: hidden; 43 | transition: inherit; 44 | display: none; 45 | } 46 | 47 | .textify .char::before, 48 | .textify .char::after { 49 | content: attr(data-char); 50 | } 51 | 52 | .textify .word::after, 53 | .textify .word::before { 54 | content: attr(data-word); 55 | } 56 | 57 | .textify .line::after, 58 | .textify .line::before { 59 | content: attr(data-line); 60 | } 61 | 62 | /* ------------------------------------------- */ 63 | 64 | .textify .line-box, 65 | .textify .line { 66 | overflow: hidden; 67 | } 68 | -------------------------------------------------------------------------------- /dist/Textify.min.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see Textify.min.js.LICENSE.txt */ 2 | !function(D,u){"object"==typeof exports&&"object"==typeof module?module.exports=u():"function"==typeof define&&define.amd?define([],u):"object"==typeof exports?exports["Textify.js"]=u():D["Textify.js"]=u()}(this,function(){"use strict";var e={d:function(D,u){for(var t in u)e.o(u,t)&&!e.o(D,t)&&Object.defineProperty(D,t,{enumerable:!0,get:u[t]})}},D=(e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(D){if("object"==typeof window)return window}}(),e.o=function(D,u){return Object.prototype.hasOwnProperty.call(D,u)},{});function i(D,u){(null==u||u>D.length)&&(u=D.length);for(var t=0,e=new Array(u);t":">")}}function G(D,u){for(var t=u.length;-1<--t;)D.push(u[t])}function z(D,u,t){for(var e;D&&D!==u;){if(e=D._next||D.nextSibling)return e.textContent.charAt(0)===t;D=D.parentNode||D._parent}}function $(D){for(var u,t=c(D.childNodes),e=t.length,i=0;i",p=1,f=u.specialChars?"function"==typeof u.specialChars?u.specialChars:b:null,h=Q.createElement("div"),m=D.parentNode;for(m.insertBefore(h,D),h.textContent=D.nodeValue,m.removeChild(D),h=-1!==(i=function D(u){var t=u.nodeType,e="";if(1===t||9===t||11===t){if("string"==typeof u.textContent)return u.textContent;for(u=u.firstChild;u;u=u.nextSibling)e+=D(u)}else if(3===t||4===t)return u.nodeValue;return e}(D=h)).indexOf("<"),!1!==u.reduceWhiteSpace&&(i=i.replace(A," ").replace(g,"")),o=(i=h?i.split("<").join("{{LT}}"):i).length,n=(" "===i.charAt(0)?c:"")+t(),s=0;s":a,s+=l-1;else if(a===E&&i.charAt(s-1)!==E&&s){for(n+=p?d:"",p=0;i.charAt(s+1)===E;)n+=c,s++;s===o-1?n+=c:")"!==i.charAt(s+1)&&(n+=c+t(),p=1)}else"{"===a&&"{{LT}}"===i.substr(s,6)?(n+=C?e()+"{{LT}}":"{{LT}}",s+=5):55296<=a.charCodeAt(0)&&a.charCodeAt(0)<=56319||65024<=i.charCodeAt(s+1)&&i.charCodeAt(s+1)<=65039?(r=((i.substr(s,12).split(y)||[])[1]||"").length||2,n+=C&&" "!==a?e()+i.substr(s,r)+"":i.substr(s,r),s+=r-1):n+=C&&" "!==a?e()+a+"":a;D.outerHTML=n+(p?d:""),h&&DD(m,"{{LT}}","<")}function K(D,u,t,e){var i,n,s=c(D.childNodes),r=s.length,o=Z(u);if(3!==D.nodeType||1X&&("BR"!==s.nodeName||0===e)&&(S.push(a=[]),A=E),P&&(s._x=s.offsetLeft,s._y=E,s._w=s.offsetWidth,s._h=s.offsetHeight),S)&&((s._isSplit&&o||!_&&o||O&&o||!O&&s.parentNode.parentNode===p&&!s.parentNode._isSplit)&&(a.push(s),s._x-=g,z(s,p,w))&&(s._wordEnd=!0),"BR"===s.nodeName)&&(s.nextSibling&&"BR"===s.nextSibling.nodeName||0===e)&&S.push([]);for(e=0;ep.clientHeight&&(p.style.height=d-R+"px",p.clientHeightp.clientWidth)&&(p.style.width=c-V+"px",p.clientWidth 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Textify.js - Next Generation Text Animation Library 19 | 20 | 21 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 |
42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /docs/main-site/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main-site", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "vite build", 8 | "preview": "vite preview", 9 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs --fix --ignore-path .gitignore", 10 | "format": "prettier --write src/" 11 | }, 12 | "dependencies": { 13 | "vue": "^3.3.4", 14 | "vue-router": "^4.2.2" 15 | }, 16 | "devDependencies": { 17 | "@rushstack/eslint-patch": "^1.2.0", 18 | "@vitejs/plugin-vue": "^4.2.3", 19 | "@vue/eslint-config-prettier": "^7.1.0", 20 | "eslint": "^8.39.0", 21 | "eslint-plugin-vue": "^9.11.0", 22 | "fontfaceobserver": "^2.3.0", 23 | "gsap": "^3.12.2", 24 | "node-sass": "^9.0.0", 25 | "prettier": "^2.8.8", 26 | "sass": "^1.64.0", 27 | "sass-loader": "^13.3.2", 28 | "textify.js": "^3.0.1", 29 | "vite": "^4.3.9" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /docs/main-site/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /docs/main-site/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /docs/main-site/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/apple-touch-icon.png -------------------------------------------------------------------------------- /docs/main-site/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/favicon-16x16.png -------------------------------------------------------------------------------- /docs/main-site/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/favicon-32x32.png -------------------------------------------------------------------------------- /docs/main-site/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MAGGIx1404/Textify-js/72764ca713b6358e3b566fc188d9169cd1fbb04d/docs/main-site/public/favicon.ico -------------------------------------------------------------------------------- /docs/main-site/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Textify.js", 3 | "short_name": "Textify.js", 4 | "description": "Textify.js is a animation engine for web typography animations, which are use to create smooth, creative or seamless animations of typography. Also it’s provide multiple animations types or custom animations on GSAP's power.", 5 | "start_url": "/", 6 | "scope": "/", 7 | "icons": [ 8 | { 9 | "src": "/android-chrome-192x192.png", 10 | "sizes": "192x192", 11 | "type": "image/png" 12 | }, 13 | { 14 | "src": "/android-chrome-512x512.png", 15 | "sizes": "512x512", 16 | "type": "image/png" 17 | } 18 | ], 19 | "theme_color": "#3fcf8e", 20 | "background_color": "#1c1c1c", 21 | "display": "standalone" 22 | } 23 | -------------------------------------------------------------------------------- /docs/main-site/src/App.vue: -------------------------------------------------------------------------------- 1 | 32 | 33 | 56 | 57 | 68 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/restart.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/base/_base.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body { 3 | width: 100%; 4 | min-height: 100%; 5 | height: auto; 6 | } 7 | 8 | * { 9 | scroll-behavior: smooth; 10 | } 11 | 12 | html { 13 | box-sizing: border-box; 14 | font-size: calc(100vw / 1920 * 10); 15 | -moz-osx-font-smoothing: grayscale; 16 | -webkit-font-smoothing: antialiased; 17 | 18 | @media only screen and (max-width: 812px) { 19 | font-size: calc(100vw / 375 * 10) !important; 20 | } 21 | } 22 | 23 | body { 24 | overscroll-behavior: none; 25 | font-family: $font-inter; 26 | font-weight: 400; 27 | font-display: swap; 28 | color: $color-light; 29 | background: $color-dark; 30 | } 31 | 32 | *, 33 | *:before, 34 | *:after { 35 | box-sizing: inherit; 36 | outline: none; 37 | -webkit-touch-callout: none; 38 | } 39 | 40 | :focus { 41 | outline: none; 42 | } 43 | 44 | ::-moz-focus-inner { 45 | border: 0; 46 | } 47 | 48 | a { 49 | color: inherit; 50 | outline: none; 51 | pointer-events: auto; 52 | text-decoration: none; 53 | } 54 | 55 | button { 56 | background: none; 57 | border: none; 58 | border-radius: none; 59 | color: inherit; 60 | font: inherit; 61 | outline: none; 62 | cursor: pointer; 63 | } 64 | 65 | img { 66 | max-width: 100%; 67 | vertical-align: middle; 68 | object-fit: cover; 69 | } 70 | 71 | input, 72 | textarea { 73 | appearance: none; 74 | background: none; 75 | border: none; 76 | border-radius: 0; 77 | outline: none; 78 | pointer-events: auto; 79 | } 80 | 81 | .container { 82 | max-width: 90%; 83 | width: 100%; 84 | margin: 0 auto; 85 | } 86 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/base/_reset.scss: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | margin: 0; 83 | padding: 0; 84 | border: 0; 85 | font-size: 100%; 86 | font: inherit; 87 | vertical-align: baseline; 88 | } 89 | 90 | article, 91 | aside, 92 | details, 93 | figcaption, 94 | figure, 95 | footer, 96 | header, 97 | hgroup, 98 | menu, 99 | nav, 100 | section { 101 | display: block; 102 | } 103 | 104 | body { 105 | line-height: 1; 106 | } 107 | 108 | ol, 109 | ul { 110 | list-style: none; 111 | } 112 | 113 | ::-webkit-scrollbar { 114 | display: none; 115 | } 116 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/base/_typography.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800;900&display=swap'); 2 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/components/_codebox.scss: -------------------------------------------------------------------------------- 1 | .codebox { 2 | width: 100%; 3 | height: auto; 4 | border-radius: 10px; 5 | background-color: $color-dark-2; 6 | border: 2px solid $color-dark-3; 7 | gap: 1rem; 8 | padding: 2rem 4rem 2rem 2rem; 9 | } 10 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/components/_footer.scss: -------------------------------------------------------------------------------- 1 | .footer { 2 | width: 100%; 3 | height: auto; 4 | border-top: 2px solid $color-dark-3; 5 | 6 | .container { 7 | padding: 5rem 0; 8 | text-align: center; 9 | 10 | p { 11 | color: rgba($color: $color-light, $alpha: 0.5); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/components/_navigation.scss: -------------------------------------------------------------------------------- 1 | nav.navigation { 2 | width: 100%; 3 | height: auto; 4 | background: $color-dark; 5 | border-bottom: 2px solid $color-dark-2; 6 | position: fixed; 7 | top: 0; 8 | left: 0; 9 | z-index: 100; 10 | 11 | .container { 12 | padding: 2rem 0; 13 | 14 | .navigation-links { 15 | width: auto; 16 | height: auto; 17 | gap: 5rem; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/components/_sidebar.scss: -------------------------------------------------------------------------------- 1 | .sidebar { 2 | width: 30rem; 3 | height: 100vh; 4 | position: fixed; 5 | top: 0; 6 | left: 0; 7 | z-index: 10; 8 | border-right: 2px solid $color-dark-3; 9 | padding: 15rem 0 5rem 5%; 10 | 11 | ul { 12 | width: 100%; 13 | height: auto; 14 | gap: 2rem; 15 | 16 | li { 17 | width: 100%; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './utils/variables'; 2 | 3 | @import './shared/texts'; 4 | @import './shared/links'; 5 | @import './shared/sections'; 6 | 7 | @import './layouts/scroll'; 8 | 9 | @import './base/typography'; 10 | @import './base/reset'; 11 | @import './base/base'; 12 | 13 | @import './layouts/flexbox'; 14 | 15 | @import './components/navigation'; 16 | @import './components/footer'; 17 | @import './components/codebox'; 18 | @import './components/sidebar'; 19 | 20 | @import './pages/home'; 21 | @import './pages/example'; 22 | @import './pages/docs'; 23 | @import './pages/tutorial'; 24 | @import './pages/notfound'; 25 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/layouts/_flexbox.scss: -------------------------------------------------------------------------------- 1 | .flex-row-center { 2 | display: flex; 3 | flex-direction: row; 4 | align-items: center; 5 | justify-content: center; 6 | } 7 | 8 | .flex-row-start-between { 9 | display: flex; 10 | flex-direction: row; 11 | align-items: flex-start; 12 | justify-content: space-between; 13 | } 14 | 15 | .flex-row-center-between { 16 | display: flex; 17 | flex-direction: row; 18 | justify-content: space-between; 19 | align-items: center; 20 | } 21 | 22 | .flex-row-center-end { 23 | display: flex; 24 | flex-direction: row; 25 | justify-content: flex-end; 26 | align-items: center; 27 | } 28 | 29 | .flex-row-center-start { 30 | display: flex; 31 | flex-direction: row; 32 | justify-content: flex-start; 33 | align-items: center; 34 | } 35 | 36 | .flex-row-start { 37 | display: flex; 38 | flex-direction: row; 39 | justify-content: flex-start; 40 | align-items: flex-start; 41 | } 42 | 43 | .flex-column-start-end { 44 | display: flex; 45 | flex-direction: column; 46 | align-items: flex-start; 47 | justify-content: flex-end; 48 | } 49 | 50 | .flex-column-center-center { 51 | display: flex; 52 | flex-direction: column; 53 | align-items: center; 54 | justify-content: center; 55 | } 56 | 57 | .flex-column-start { 58 | display: flex; 59 | flex-direction: column; 60 | align-items: flex-start; 61 | justify-content: flex-start; 62 | } 63 | 64 | .flex-column-start-center { 65 | display: flex; 66 | flex-direction: column; 67 | align-items: flex-start; 68 | justify-content: center; 69 | } 70 | 71 | .flex-column-center-between { 72 | display: flex; 73 | flex-direction: column; 74 | align-items: center; 75 | justify-content: space-between; 76 | } 77 | 78 | .flex-column-end-center { 79 | display: flex; 80 | flex-direction: column; 81 | align-items: flex-end; 82 | justify-content: center; 83 | } 84 | 85 | .flex-column-end-start { 86 | display: flex; 87 | flex-direction: column; 88 | align-items: flex-end; 89 | justify-content: flex-start; 90 | } 91 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/layouts/_scroll.scss: -------------------------------------------------------------------------------- 1 | .main-layout { 2 | width: 100%; 3 | height: 100vh; 4 | overflow-x: hidden; 5 | overflow-y: scroll; 6 | 7 | &-scroller { 8 | width: 100%; 9 | min-height: 100vh; 10 | height: auto; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/pages/_docs.scss: -------------------------------------------------------------------------------- 1 | .docs { 2 | padding-left: 30rem; 3 | 4 | .banner { 5 | width: 100%; 6 | height: auto; 7 | padding: 12rem 0; 8 | 9 | .container { 10 | background: $color-dark-2; 11 | border-radius: 10px; 12 | text-align: center; 13 | padding: 10rem 0; 14 | 15 | h1 { 16 | font-weight: bold; 17 | } 18 | } 19 | } 20 | 21 | .doc-section { 22 | .container { 23 | padding-bottom: 10rem; 24 | gap: 4rem; 25 | 26 | .doc-box { 27 | width: 100%; 28 | height: auto; 29 | gap: 2rem; 30 | 31 | p { 32 | width: 80%; 33 | } 34 | 35 | ul.options-list { 36 | width: 100%; 37 | height: auto; 38 | 39 | li { 40 | width: 100%; 41 | height: auto; 42 | padding: 2rem 0; 43 | border-bottom: 2px solid $color-dark-3; 44 | display: flex; 45 | align-items: start; 46 | justify-content: space-between; 47 | 48 | &.no-border { 49 | border: none; 50 | } 51 | 52 | p { 53 | width: 100%; 54 | } 55 | 56 | .option-name { 57 | flex: 0 0 10%; 58 | max-width: 10%; 59 | } 60 | 61 | .context { 62 | flex: 0 0 85%; 63 | max-width: 85%; 64 | width: 100%; 65 | height: auto; 66 | display: flex; 67 | flex-direction: column; 68 | align-items: flex-start; 69 | justify-content: flex-start; 70 | gap: 1.5rem; 71 | } 72 | } 73 | } 74 | } 75 | } 76 | } 77 | 78 | .thanks { 79 | min-height: auto; 80 | .container { 81 | text-align: center; 82 | padding: 5rem 0 15rem 0; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/pages/_example.scss: -------------------------------------------------------------------------------- 1 | .example { 2 | .banner { 3 | .container { 4 | padding-top: 20rem; 5 | text-align: center; 6 | 7 | .paragraph { 8 | width: 60%; 9 | margin-top: 2rem; 10 | } 11 | 12 | .btns { 13 | margin-top: 4rem; 14 | } 15 | 16 | .bg-line { 17 | margin-top: 10rem; 18 | } 19 | } 20 | } 21 | 22 | .animation-section { 23 | padding: 10rem 0; 24 | 25 | .top-content { 26 | text-align: center; 27 | } 28 | 29 | .container { 30 | flex-wrap: wrap; 31 | justify-content: space-between; 32 | row-gap: 4rem; 33 | margin-top: 5rem; 34 | 35 | .animationbox-wrapper { 36 | width: 49%; 37 | height: 40rem; 38 | 39 | .animation-box { 40 | width: 100%; 41 | height: 100%; 42 | position: relative; 43 | background: $color-dark-2; 44 | border: 1px solid $color-dark-3; 45 | border-radius: 10px; 46 | display: grid; 47 | place-items: center; 48 | 49 | &.content-animation-box { 50 | padding: 10rem; 51 | } 52 | 53 | .code-btn { 54 | position: absolute; 55 | top: 1rem; 56 | right: 1rem; 57 | background: $color-dark-3; 58 | border: 1px solid $color-dark-3; 59 | border-radius: 5px; 60 | padding: 1rem 2rem; 61 | cursor: pointer; 62 | transition: all 0.5s ease; 63 | 64 | &:hover { 65 | background: $color-dark-2; 66 | border: 1px solid $color-dark-3; 67 | } 68 | 69 | &:nth-of-type(1) { 70 | right: 8rem; 71 | } 72 | 73 | img { 74 | width: 2rem; 75 | height: 2rem; 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | h1.sub-title { 83 | text-align: center; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/pages/_home.scss: -------------------------------------------------------------------------------- 1 | .home { 2 | .banner { 3 | .container { 4 | padding-top: 20rem; 5 | text-align: center; 6 | 7 | .paragraph { 8 | width: 60%; 9 | margin-top: 2rem; 10 | } 11 | 12 | .btns { 13 | margin-top: 3rem; 14 | gap: 2rem; 15 | } 16 | 17 | .bg-line { 18 | margin-top: 10rem; 19 | } 20 | } 21 | } 22 | 23 | .guide { 24 | width: 100%; 25 | height: auto; 26 | 27 | .container { 28 | padding: 20rem 0; 29 | border-bottom: 2px solid $color-dark-3; 30 | 31 | h1.sub-title { 32 | text-align: center; 33 | } 34 | 35 | ul { 36 | width: 100%; 37 | height: auto; 38 | margin-top: 5rem; 39 | align-items: stretch; 40 | 41 | li { 42 | flex: 0 0 24%; 43 | max-width: 24%; 44 | width: 100%; 45 | height: auto; 46 | background: $color-dark-2; 47 | border: 2px solid $color-dark-3; 48 | border-radius: 10px; 49 | padding: 4rem; 50 | 51 | p { 52 | margin-top: 2rem; 53 | } 54 | } 55 | } 56 | 57 | .extra { 58 | text-align: center; 59 | margin-top: 4rem; 60 | } 61 | } 62 | } 63 | 64 | .extra-section { 65 | width: 100%; 66 | height: auto; 67 | 68 | .container { 69 | padding: 20rem 0; 70 | text-align: center; 71 | 72 | h1 { 73 | margin-bottom: 4rem; 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/pages/_notfound.scss: -------------------------------------------------------------------------------- 1 | .notfound { 2 | .banner { 3 | width: 100%; 4 | height: 100vh; 5 | text-align: center; 6 | gap: 2rem; 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/pages/_tutorial.scss: -------------------------------------------------------------------------------- 1 | .tutorial { 2 | .banner { 3 | width: 100%; 4 | height: 100vh; 5 | overflow: hidden; 6 | 7 | .container { 8 | height: 100vh; 9 | gap: 2rem; 10 | text-align: center; 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/shared/_links.scss: -------------------------------------------------------------------------------- 1 | .logo { 2 | font-size: 3rem; 3 | font-weight: 600; 4 | color: $color-green-2; 5 | margin-right: 4rem; 6 | } 7 | 8 | .navigation-link { 9 | font-size: 1.8rem; 10 | font-weight: 400; 11 | letter-spacing: 0.5px; 12 | } 13 | 14 | .bg-link { 15 | font-size: 1.8rem; 16 | font-weight: 400; 17 | letter-spacing: 0.5px; 18 | background: $color-green; 19 | border: 1px solid $color-green-2; 20 | border-radius: 10px; 21 | padding: 1.25rem 2.5rem; 22 | 23 | &-dark { 24 | background: $color-dark-2; 25 | border: 1px solid $color-dark-3; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/shared/_sections.scss: -------------------------------------------------------------------------------- 1 | section { 2 | width: 100%; 3 | height: auto; 4 | min-height: 50vh; 5 | } 6 | 7 | .bg-line { 8 | width: 90%; 9 | height: 2px; 10 | background: $color-dark-2; 11 | } 12 | 13 | .bg-line-full { 14 | width: 100%; 15 | height: 2px; 16 | background: $color-dark-2; 17 | } 18 | 19 | .responsive { 20 | width: 100%; 21 | height: 100vh; 22 | text-align: center; 23 | gap: 2rem; 24 | } 25 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/shared/_texts.scss: -------------------------------------------------------------------------------- 1 | .title { 2 | font-size: 10rem; 3 | font-weight: 500; 4 | line-height: 1.2; 5 | letter-spacing: 0.5px; 6 | 7 | // title animation 8 | .char { 9 | &::after { 10 | visibility: visible !important; 11 | top: 100%; 12 | content: attr(data-char); 13 | display: block; 14 | } 15 | 16 | &::before { 17 | visibility: visible !important; 18 | top: -100%; 19 | content: attr(data-char); 20 | display: block; 21 | } 22 | } 23 | 24 | &-green { 25 | color: $color-green-2; 26 | } 27 | } 28 | 29 | .sub-title { 30 | font-size: 5rem; 31 | font-weight: 400; 32 | line-height: 1.2; 33 | letter-spacing: 0.5px; 34 | } 35 | 36 | .small-title { 37 | font-size: 2.25rem; 38 | font-weight: 500; 39 | line-height: 1.2; 40 | letter-spacing: 0.5px; 41 | color: $color-green-2; 42 | } 43 | 44 | .paragraph { 45 | font-size: 2.2rem; 46 | font-weight: 400; 47 | line-height: 1.5; 48 | } 49 | 50 | .small-paragraph { 51 | font-size: 1.5rem; 52 | font-weight: 400; 53 | line-height: 1.3; 54 | } 55 | 56 | .content { 57 | font-size: 1.6rem; 58 | font-weight: 400; 59 | line-height: 1.5; 60 | color: $color-light-2; 61 | 62 | &.note { 63 | color: $color-red; 64 | font-weight: 500; 65 | } 66 | 67 | .tag { 68 | background: $color-dark-2; 69 | padding: 0.3rem 1rem; 70 | border-radius: 10px; 71 | border: 1px solid $color-dark-3; 72 | } 73 | } 74 | 75 | .bold { 76 | font-weight: 600; 77 | } 78 | 79 | .green { 80 | color: $color-green-2; 81 | } 82 | 83 | .html-color { 84 | color: $color-red; 85 | } 86 | -------------------------------------------------------------------------------- /docs/main-site/src/assets/styles/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | // fonts 2 | $font-inter: 'Inter', sans-serif; 3 | 4 | // colors 5 | $color-dark: #1c1c1c; 6 | $color-dark-2: #232323; 7 | $color-dark-3: #282828; 8 | $color-dark-4: #343434; 9 | 10 | $color-green: #2b825b; 11 | $color-green-2: #3fcf8e; 12 | 13 | $color-red: #b02e0c; 14 | 15 | $color-light: #ededed; 16 | $color-light-2: #bbbbbb; 17 | -------------------------------------------------------------------------------- /docs/main-site/src/components/AnimationBox.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /docs/main-site/src/components/CodeBox.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /docs/main-site/src/components/FooterBox.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /docs/main-site/src/components/LargeAnimationBox.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /docs/main-site/src/components/Navigation.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /docs/main-site/src/components/SideBar.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /docs/main-site/src/layouts/MainLayout.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/main-site/src/library/Textify.min.css: -------------------------------------------------------------------------------- 1 | /* 2 | fix safari custom kerning by adding a space after each character 3 | */ 4 | * { 5 | -webkit-font-feature-settings: "kern" 1; 6 | -moz-font-feature-settings: "kern" 1; 7 | -ms-font-feature-settings: "kern" 1; 8 | -o-font-feature-settings: "kern" 1; 9 | font-feature-settings: "kern" 1; 10 | font-kerning: none; 11 | -webkit-text-rendering: optimizeSpeed; 12 | text-rendering: optimizeSpeed; 13 | -webkit-transform: translateZ(0); 14 | transform: translateZ(0); 15 | } 16 | 17 | /* ----------------------------------------- */ 18 | .textify .word, 19 | .textify .char { 20 | display: inline-block; 21 | will-change: transform, opacity; /* for safari */ 22 | } 23 | 24 | /* ----------------------------------------- */ 25 | 26 | .textify .char { 27 | position: relative; 28 | } 29 | 30 | /** 31 | * Populate the psuedo elements with the character to allow for expanded effects 32 | * Set to `display: none` by default; just add `display: block` when you want 33 | * to use the psuedo elements 34 | */ 35 | .textify .char::before, 36 | .textify .char::after, 37 | .textify .word::after, 38 | .textify .word::before, 39 | .textify .line::after, 40 | .textify .line::before { 41 | position: absolute; 42 | top: 0; 43 | left: 0; 44 | visibility: hidden; 45 | transition: inherit; 46 | display: none; 47 | } 48 | 49 | .textify .char::before, 50 | .textify .char::after { 51 | content: attr(data-char); 52 | } 53 | 54 | .textify .word::after, 55 | .textify .word::before { 56 | content: attr(data-word); 57 | } 58 | 59 | .textify .line::after, 60 | .textify .line::before { 61 | content: attr(data-line); 62 | } 63 | 64 | /* ------------------------------------------- */ 65 | 66 | .textify .line-box, 67 | .textify .line { 68 | overflow: hidden; 69 | } 70 | -------------------------------------------------------------------------------- /docs/main-site/src/library/Textify.min.js: -------------------------------------------------------------------------------- 1 | /*! For license information please see Textify.min.js.LICENSE.txt */ 2 | !function(D,u){"object"==typeof exports&&"object"==typeof module?module.exports=u():"function"==typeof define&&define.amd?define([],u):"object"==typeof exports?exports["Textify.js"]=u():D["Textify.js"]=u()}(this,function(){"use strict";var e={d:function(D,u){for(var t in u)e.o(u,t)&&!e.o(D,t)&&Object.defineProperty(D,t,{enumerable:!0,get:u[t]})}},D=(e.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(D){if("object"==typeof window)return window}}(),e.o=function(D,u){return Object.prototype.hasOwnProperty.call(D,u)},{});function i(D,u){(null==u||u>D.length)&&(u=D.length);for(var t=0,e=new Array(u);t":">")}}function G(D,u){for(var t=u.length;-1<--t;)D.push(u[t])}function z(D,u,t){for(var e;D&&D!==u;){if(e=D._next||D.nextSibling)return e.textContent.charAt(0)===t;D=D.parentNode||D._parent}}function $(D){for(var u,t=c(D.childNodes),e=t.length,i=0;i",d=1,f=u.specialChars?"function"==typeof u.specialChars?u.specialChars:b:null,E=Q.createElement("div"),m=D.parentNode;for(m.insertBefore(E,D),E.textContent=D.nodeValue,m.removeChild(D),E=-1!==(i=function D(u){var t=u.nodeType,e="";if(1===t||9===t||11===t){if("string"==typeof u.textContent)return u.textContent;for(u=u.firstChild;u;u=u.nextSibling)e+=D(u)}else if(3===t||4===t)return u.nodeValue;return e}(D=E)).indexOf("<"),!1!==u.reduceWhiteSpace&&(i=i.replace(g," ").replace(A,"")),o=(i=E?i.split("<").join("{{LT}}"):i).length,n=(" "===i.charAt(0)?c:"")+t(),s=0;s":a,s+=l-1;else if(a===h&&i.charAt(s-1)!==h&&s){for(n+=d?p:"",d=0;i.charAt(s+1)===h;)n+=c,s++;s===o-1?n+=c:")"!==i.charAt(s+1)&&(n+=c+t(),d=1)}else"{"===a&&"{{LT}}"===i.substr(s,6)?(n+=C?e()+"{{LT}}":"{{LT}}",s+=5):55296<=a.charCodeAt(0)&&a.charCodeAt(0)<=56319||65024<=i.charCodeAt(s+1)&&i.charCodeAt(s+1)<=65039?(r=((i.substr(s,12).split(y)||[])[1]||"").length||2,n+=C&&" "!==a?e()+i.substr(s,r)+"":i.substr(s,r),s+=r-1):n+=C&&" "!==a?e()+a+"":a;D.outerHTML=n+(d?p:""),E&&DD(m,"{{LT}}","<")}function K(D,u,t,e){var i,n,s=c(D.childNodes),r=s.length,o=Z(u);if(3!==D.nodeType||1X&&("BR"!==s.nodeName||0===e)&&(S.push(a=[]),g=h),N&&(s._x=s.offsetLeft,s._y=h,s._w=s.offsetWidth,s._h=s.offsetHeight),S)&&((s._isSplit&&o||!_&&o||O&&o||!O&&s.parentNode.parentNode===d&&!s.parentNode._isSplit)&&(a.push(s),s._x-=A,z(s,d,w))&&(s._wordEnd=!0),"BR"===s.nodeName)&&(s.nextSibling&&"BR"===s.nextSibling.nodeName||0===e)&&S.push([]);for(e=0;ed.clientHeight&&(d.style.height=p-R+"px",d.clientHeightd.clientWidth)&&(d.style.width=c-V+"px",d.clientWidth { 17 | app.mount('#app') 18 | }) 19 | -------------------------------------------------------------------------------- /docs/main-site/src/router/index.js: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import Home from '../views/Home.vue' 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'home', 10 | component: Home 11 | }, 12 | { 13 | path: '/example', 14 | name: 'example', 15 | component: () => import('@/views/Example.vue') 16 | }, 17 | { 18 | path: '/documentation', 19 | name: 'documentation', 20 | component: () => import('@/views/Documentation.vue') 21 | }, 22 | { 23 | path: '/tutorial', 24 | name: 'tutorial', 25 | component: () => import('@/views/Tutorial.vue') 26 | }, 27 | { 28 | path: '/:pathMatch(.*)*', 29 | name: 'not-found', 30 | component: () => import('@/views/NotFound.vue') 31 | } 32 | ] 33 | }) 34 | 35 | export default router 36 | -------------------------------------------------------------------------------- /docs/main-site/src/views/Documentation.vue: -------------------------------------------------------------------------------- 1 | 553 | 554 | 566 | 567 | 568 | -------------------------------------------------------------------------------- /docs/main-site/src/views/Example.vue: -------------------------------------------------------------------------------- 1 | 48 | 49 | 188 | 189 | 190 | -------------------------------------------------------------------------------- /docs/main-site/src/views/Home.vue: -------------------------------------------------------------------------------- 1 | 62 | 63 | 112 | -------------------------------------------------------------------------------- /docs/main-site/src/views/NotFound.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /docs/main-site/src/views/Tutorial.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /docs/main-site/vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] 3 | } 4 | -------------------------------------------------------------------------------- /docs/main-site/vite.config.js: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | vue(), 10 | ], 11 | resolve: { 12 | alias: { 13 | '@': fileURLToPath(new URL('./src', import.meta.url)) 14 | } 15 | } 16 | }) 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "textify.js", 3 | "version": "3.0.1", 4 | "description": "Next Generation Text Animation Library", 5 | "main": "./dist/Textify.min.js", 6 | "module": "./src/index.js", 7 | "keywords": [ 8 | "Text-Animation", 9 | "Text-Animation-Library", 10 | "scroll-based-text-animation", 11 | "textify.js" 12 | ], 13 | "scripts": { 14 | "dev": "webpack --mode development --watch", 15 | "build": "webpack", 16 | "lint": "eslint \"./src/**/*.js\" --fix", 17 | "format": "prettier --write \"./src/**/*.js\"", 18 | "start": "webpack-dev-server --mode development --config webpack.config.js" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/MAGGIx1404/Textify.js.git" 23 | }, 24 | "author": "Jeet (MAGGIx1404)", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/MAGGIx1404/Textify.js/issues" 28 | }, 29 | "homepage": "https://github.com/MAGGIx1404/Textify.js#readme", 30 | "devDependencies": { 31 | "@babel/core": "^7.18.6", 32 | "@babel/eslint-parser": "^7.18.9", 33 | "@babel/plugin-transform-runtime": "^7.18.6", 34 | "@babel/preset-env": "^7.18.6", 35 | "babel-loader": "^9.1.2", 36 | "copy-webpack-plugin": "^11.0.0", 37 | "css-loader": "^6.7.3", 38 | "eslint": "^8.19.0", 39 | "eslint-config-prettier": "^8.5.0", 40 | "prettier": "^3.0.0", 41 | "size-plugin": "^3.0.0", 42 | "style-loader": "^3.3.1", 43 | "terser-webpack-plugin": "^5.3.3", 44 | "uglify-js": "^3.16.2", 45 | "webpack": "^5.73.0", 46 | "webpack-cli": "^5.0.1", 47 | "webpack-dev-server": "^4.9.3" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Utils/Break.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-misleading-character-class */ 2 | 3 | let _trimExp = /(^\s+|\s+$)/g; 4 | 5 | export const emojiExp = 6 | /([\uD800-\uDBFF][\uDC00-\uDFFF](?:[\u200D\uFE0F][\uD800-\uDBFF][\uDC00-\uDFFF]){2,}|\uD83D\uDC69(?:\u200D(?:(?:\uD83D\uDC69\u200D)?\uD83D\uDC67|(?:\uD83D\uDC69\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D(?:\uD83D\uDC69\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]\uFE0F|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC6F\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3C-\uDD3E\uDDD6-\uDDDF])\u200D[\u2640\u2642]\uFE0F|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF6\uD83C\uDDE6|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uFE0F\u200D[\u2640\u2642]|(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642])\uFE0F|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\uD83D\uDC69\u200D[\u2695\u2696\u2708]|\uD83D\uDC68(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708]))\uFE0F|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83D\uDC69\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69]))|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67)\uDB40\uDC7F|\uD83D\uDC68(?:\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66\u200D\uD83D\uDC66|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92])|(?:\uD83C[\uDFFB-\uDFFF])\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]))|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDD1-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|\uD83D\uDC68(?:\u200D(?:(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC67|(?:(?:\uD83D[\uDC68\uDC69])\u200D)?\uD83D\uDC66)|\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC69\uDC6E\uDC70-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD18-\uDD1C\uDD1E\uDD1F\uDD26\uDD30-\uDD39\uDD3D\uDD3E\uDDD1-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])?|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDEEB\uDEEC\uDEF4-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u2660\u2663\u2665\u2666\u2668\u267B\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEF8]|\uD83E[\uDD10-\uDD3A\uDD3C-\uDD3E\uDD40-\uDD45\uDD47-\uDD4C\uDD50-\uDD6B\uDD80-\uDD97\uDDC0\uDDD0-\uDDE6])\uFE0F)/; 7 | 8 | export function getText(e) { 9 | let type = e.nodeType, 10 | result = ""; 11 | if (type === 1 || type === 9 || type === 11) { 12 | if (typeof e.textContent === "string") { 13 | return e.textContent; 14 | } else { 15 | for (e = e.firstChild; e; e = e.nextSibling) { 16 | result += getText(e); 17 | } 18 | } 19 | } else if (type === 3 || type === 4) { 20 | return e.nodeValue; 21 | } 22 | return result; 23 | } 24 | 25 | export function splitInnerHTML(element, delimiter, trim) { 26 | let node = element.firstChild, 27 | result = []; 28 | while (node) { 29 | if (node.nodeType === 3) { 30 | result.push(...emojiSafeSplit((node.nodeValue + "").replace(/^\n+/g, "").replace(/\s+/g, " "), delimiter, trim)); 31 | } else if ((node.nodeName + "").toLowerCase() === "br") { 32 | result[result.length - 1] += "
"; 33 | } else { 34 | result.push(node.outerHTML); 35 | } 36 | node = node.nextSibling; 37 | } 38 | return result; 39 | } 40 | 41 | export function emojiSafeSplit(text, delimiter, trim) { 42 | text += ""; // make sure it's cast as a string. Someone may pass in a number. 43 | if (trim) { 44 | text = text.replace(_trimExp, ""); 45 | } 46 | if (delimiter && delimiter !== "") { 47 | return text.replace(/>/g, ">").replace(/= 0xd800 && character.charCodeAt(0) <= 0xdbff) || 58 | (text.charCodeAt(i + 1) >= 0xfe00 && text.charCodeAt(i + 1) <= 0xfe0f) 59 | ) { 60 | //special emoji characters use 2 or 4 unicode characters that we must keep together. 61 | j = ((text.substr(i, 12).split(emojiExp) || [])[1] || "").length || 2; 62 | character = text.substr(i, j); 63 | result.emoji = 1; 64 | i += j - 1; 65 | } 66 | result.push(character === ">" ? ">" : character === "<" ? "<" : character); 67 | } 68 | return result; 69 | } 70 | -------------------------------------------------------------------------------- /src/Utils/Config.js: -------------------------------------------------------------------------------- 1 | const Config = { 2 | splitType: "lines words chars", // chars or words or lines 3 | tag: "div", // span or div 4 | charsClass: "char", // class name for chars 5 | wordsClass: "word", // class name for words 6 | linesClass: "line", // class name for lines 7 | position: "relative", // absolute or relative 8 | largeText: false, // true or false 9 | observer: { 10 | repeat: false, // true or false 11 | threshold: 0.5 // 0.0 ~ 1.0 12 | }, 13 | animation: { 14 | by: "chars", // chars or words or lines 15 | duration: 0.5, // seconds 16 | stagger: 0.05, // seconds 17 | delay: 0.0, // seconds 18 | ease: "ease", // ease or linear or cubic-bezier 19 | customAnimation: false, // true or false 20 | transformOrigin: "center center", // center center or top left or top center or top right or center right or bottom right or bottom center or bottom left or center left 21 | animateProps: { 22 | opacity: 1, // 0 ~ 1 23 | y: "100%", // -100 ~ 100 (%) 24 | x: 0, // -100 ~ 100 (%) 25 | scale: 1, // 0 ~ 1 26 | rotate: 0, // -360 ~ 360 27 | skewX: 0, // -360 ~ 360 28 | skewY: 0 // -360 ~ 360 29 | } 30 | } 31 | }; 32 | 33 | export { Config }; 34 | -------------------------------------------------------------------------------- /src/Utils/Text.js: -------------------------------------------------------------------------------- 1 | import { emojiExp, getText } from "./Break.js"; 2 | 3 | let _document, 4 | _window, 5 | _coreInitted, 6 | _stripExp = /(?:\r|\n|\t\t)/g, 7 | _multipleSpacesExp = /(?:\s\s+)/g, 8 | _initCore = () => { 9 | _document = document; 10 | _window = window; 11 | _coreInitted = 1; 12 | }, 13 | _bonusValidated = 1, 14 | _getComputedStyle = (element) => _window.getComputedStyle(element), 15 | _isArray = Array.isArray, 16 | _slice = [].slice, 17 | _toArray = (value, leaveStrings) => { 18 | let type; 19 | return _isArray(value) 20 | ? value 21 | : (type = typeof value) === "string" && !leaveStrings && value 22 | ? _slice.call(_document.querySelectorAll(value), 0) 23 | : value && type === "object" && "length" in value 24 | ? _slice.call(value, 0) 25 | : value 26 | ? [value] 27 | : []; 28 | }, 29 | _isAbsolute = (vars) => vars.position === "absolute" || vars.absolute === true, 30 | _findSpecialChars = (text, chars) => { 31 | let i = chars.length, 32 | s; 33 | while (--i > -1) { 34 | s = chars[i]; 35 | if (text.substr(0, s.length) === s) { 36 | return s.length; 37 | } 38 | } 39 | }, 40 | customStyle = " style='position:relative;display:inline-block;'", 41 | cssClass = (cssClass = "", tag) => { 42 | let iterate = ~cssClass.indexOf("++"), 43 | num = 1; 44 | if (iterate) { 45 | cssClass = cssClass.split("++").join(""); 46 | } 47 | return () => "<" + tag + customStyle + (cssClass ? " class='" + cssClass + (iterate ? num++ : "") + "'>" : ">"); 48 | }, 49 | _swapText = (element, oldText, newText) => { 50 | let type = element.nodeType; 51 | if (type === 1 || type === 9 || type === 11) { 52 | for (element = element.firstChild; element; element = element.nextSibling) { 53 | _swapText(element, oldText, newText); 54 | } 55 | } else if (type === 3 || type === 4) { 56 | element.nodeValue = element.nodeValue.split(oldText).join(newText); 57 | } 58 | }, 59 | _pushReversed = (a, merge) => { 60 | let i = merge.length; 61 | while (--i > -1) { 62 | a.push(merge[i]); 63 | } 64 | }, 65 | _isBeforeWordDelimiter = (e, root, wordDelimiter) => { 66 | let next; 67 | while (e && e !== root) { 68 | next = e._next || e.nextSibling; 69 | if (next) { 70 | return next.textContent.charAt(0) === wordDelimiter; 71 | } 72 | e = e.parentNode || e._parent; 73 | } 74 | }, 75 | _deWordify = (e) => { 76 | let children = _toArray(e.childNodes), 77 | l = children.length, 78 | i, 79 | child; 80 | for (i = 0; i < l; i++) { 81 | child = children[i]; 82 | if (child._isSplit) { 83 | _deWordify(child); 84 | } else { 85 | if (i && child.previousSibling.nodeType === 3) { 86 | child.previousSibling.nodeValue += child.nodeType === 3 ? child.nodeValue : child.firstChild.nodeValue; 87 | } else if (child.nodeType !== 3) { 88 | e.insertBefore(child.firstChild, child); 89 | } 90 | e.removeChild(child); 91 | } 92 | } 93 | }, 94 | _getStyleAsNumber = (name, computedStyle) => parseFloat(computedStyle[name]) || 0, 95 | _setPositionsAfterSplit = (element, vars, allChars, allWords, allLines, origWidth, origHeight) => { 96 | let cs = _getComputedStyle(element), 97 | paddingLeft = _getStyleAsNumber("paddingLeft", cs), 98 | lineOffsetY = -999, 99 | borderTopAndBottom = _getStyleAsNumber("borderBottomWidth", cs) + _getStyleAsNumber("borderTopWidth", cs), 100 | borderLeftAndRight = _getStyleAsNumber("borderLeftWidth", cs) + _getStyleAsNumber("borderRightWidth", cs), 101 | padTopAndBottom = _getStyleAsNumber("paddingTop", cs) + _getStyleAsNumber("paddingBottom", cs), 102 | padLeftAndRight = _getStyleAsNumber("paddingLeft", cs) + _getStyleAsNumber("paddingRight", cs), 103 | lineThreshold = _getStyleAsNumber("fontSize", cs) * 0.2, 104 | textAlign = cs.textAlign, 105 | charArray = [], 106 | wordArray = [], 107 | lineArray = [], 108 | wordDelimiter = vars.wordDelimiter || " ", 109 | tag = vars.tag ? vars.tag : vars.span ? "span" : "div", 110 | types = vars.type || vars.split || "chars,words,lines", 111 | lines = allLines && ~types.indexOf("lines") ? [] : null, 112 | words = ~types.indexOf("words"), 113 | chars = ~types.indexOf("chars"), 114 | absolute = _isAbsolute(vars), 115 | linesClass = vars.linesClass, 116 | iterateLine = ~(linesClass || "").indexOf("++"), 117 | spaceNodesToRemove = [], 118 | i, 119 | j, 120 | l, 121 | node, 122 | nodes, 123 | isChild, 124 | curLine, 125 | addWordSpaces, 126 | style, 127 | lineNode, 128 | lineWidth, 129 | offset; 130 | if (iterateLine) { 131 | linesClass = linesClass.split("++").join(""); 132 | } 133 | 134 | j = element.getElementsByTagName("*"); 135 | l = j.length; 136 | nodes = []; 137 | for (i = 0; i < l; i++) { 138 | nodes[i] = j[i]; 139 | } 140 | 141 | if (lines || absolute) { 142 | for (i = 0; i < l; i++) { 143 | node = nodes[i]; 144 | isChild = node.parentNode === element; 145 | if (isChild || absolute || (chars && !words)) { 146 | offset = node.offsetTop; 147 | if (lines && isChild && Math.abs(offset - lineOffsetY) > lineThreshold && (node.nodeName !== "BR" || i === 0)) { 148 | curLine = []; 149 | lines.push(curLine); 150 | lineOffsetY = offset; 151 | } 152 | if (absolute) { 153 | node._x = node.offsetLeft; 154 | node._y = offset; 155 | node._w = node.offsetWidth; 156 | node._h = node.offsetHeight; 157 | } 158 | if (lines) { 159 | if ( 160 | (node._isSplit && isChild) || 161 | (!chars && isChild) || 162 | (words && isChild) || 163 | (!words && node.parentNode.parentNode === element && !node.parentNode._isSplit) 164 | ) { 165 | curLine.push(node); 166 | node._x -= paddingLeft; 167 | if (_isBeforeWordDelimiter(node, element, wordDelimiter)) { 168 | node._wordEnd = true; 169 | } 170 | } 171 | if (node.nodeName === "BR" && ((node.nextSibling && node.nextSibling.nodeName === "BR") || i === 0)) { 172 | lines.push([]); 173 | } 174 | } 175 | } 176 | } 177 | } 178 | 179 | for (i = 0; i < l; i++) { 180 | node = nodes[i]; 181 | isChild = node.parentNode === element; 182 | if (node.nodeName === "BR") { 183 | if (lines || absolute) { 184 | if (node.parentNode) { 185 | node.parentNode.removeChild(node); 186 | } 187 | nodes.splice(i--, 1); 188 | l--; 189 | } else if (!words) { 190 | element.appendChild(node); 191 | } 192 | continue; 193 | } 194 | 195 | if (absolute) { 196 | style = node.style; 197 | if (!words && !isChild) { 198 | node._x += node.parentNode._x; 199 | node._y += node.parentNode._y; 200 | } 201 | style.left = node._x + "px"; 202 | style.top = node._y + "px"; 203 | style.position = "absolute"; 204 | style.display = "block"; 205 | style.width = node._w + 1 + "px"; 206 | style.height = node._h + "px"; 207 | } 208 | 209 | if (!words && chars) { 210 | if (node._isSplit) { 211 | node._next = node.nextSibling; 212 | node.parentNode.appendChild(node); 213 | } else if (node.parentNode._isSplit) { 214 | node._parent = node.parentNode; 215 | if (!node.previousSibling && node.firstChild) { 216 | node.firstChild._isFirst = true; 217 | } 218 | if (node.nextSibling && node.nextSibling.textContent === " " && !node.nextSibling.nextSibling) { 219 | spaceNodesToRemove.push(node.nextSibling); 220 | } 221 | node._next = node.nextSibling && node.nextSibling._isFirst ? null : node.nextSibling; 222 | node.parentNode.removeChild(node); 223 | nodes.splice(i--, 1); 224 | l--; 225 | } else if (!isChild) { 226 | offset = !node.nextSibling && _isBeforeWordDelimiter(node.parentNode, element, wordDelimiter); 227 | if (node.parentNode._parent) { 228 | node.parentNode._parent.appendChild(node); 229 | } 230 | if (offset) { 231 | node.parentNode.appendChild(_document.createTextNode(" ")); 232 | } 233 | if (tag === "span") { 234 | node.style.display = "inline"; 235 | } 236 | charArray.push(node); 237 | } 238 | } else if (node.parentNode._isSplit && !node._isSplit && node.innerHTML !== "") { 239 | wordArray.push(node); 240 | } else if (chars && !node._isSplit) { 241 | if (tag === "span") { 242 | node.style.display = "inline"; 243 | } 244 | charArray.push(node); 245 | } 246 | } 247 | 248 | i = spaceNodesToRemove.length; 249 | while (--i > -1) { 250 | spaceNodesToRemove[i].parentNode.removeChild(spaceNodesToRemove[i]); 251 | } 252 | 253 | if (lines) { 254 | if (absolute) { 255 | lineNode = _document.createElement(tag); 256 | element.appendChild(lineNode); 257 | lineWidth = lineNode.offsetWidth + "px"; 258 | offset = lineNode.offsetParent === element ? 0 : element.offsetLeft; 259 | element.removeChild(lineNode); 260 | } 261 | style = element.style.cssText; 262 | element.style.cssText = "display:none;"; 263 | while (element.firstChild) { 264 | element.removeChild(element.firstChild); 265 | } 266 | addWordSpaces = wordDelimiter === " " && (!absolute || (!words && !chars)); 267 | for (i = 0; i < lines.length; i++) { 268 | curLine = lines[i]; 269 | lineNode = _document.createElement(tag); 270 | lineNode.style.cssText = "display:block;text-align:" + textAlign + ";position:" + (absolute ? "absolute;" : "relative;"); 271 | if (linesClass) { 272 | lineNode.className = linesClass + (iterateLine ? i + 1 : ""); 273 | } 274 | lineArray.push(lineNode); 275 | l = curLine.length; 276 | for (j = 0; j < l; j++) { 277 | if (curLine[j].nodeName !== "BR") { 278 | node = curLine[j]; 279 | lineNode.appendChild(node); 280 | if (addWordSpaces && node._wordEnd) { 281 | lineNode.appendChild(_document.createTextNode(" ")); 282 | } 283 | if (absolute) { 284 | if (j === 0) { 285 | lineNode.style.top = node._y + "px"; 286 | lineNode.style.left = paddingLeft + offset + "px"; 287 | } 288 | node.style.top = "0px"; 289 | if (offset) { 290 | node.style.left = node._x - offset + "px"; 291 | } 292 | } 293 | } 294 | } 295 | if (l === 0) { 296 | lineNode.innerHTML = " "; 297 | } else if (!words && !chars) { 298 | _deWordify(lineNode); 299 | _swapText(lineNode, String.fromCharCode(160), " "); 300 | } 301 | if (absolute) { 302 | lineNode.style.width = lineWidth; 303 | lineNode.style.height = node._h + "px"; 304 | } 305 | element.appendChild(lineNode); 306 | } 307 | element.style.cssText = style; 308 | } 309 | 310 | if (absolute) { 311 | if (origHeight > element.clientHeight) { 312 | element.style.height = origHeight - padTopAndBottom + "px"; 313 | if (element.clientHeight < origHeight) { 314 | element.style.height = origHeight + borderTopAndBottom + "px"; 315 | } 316 | } 317 | if (origWidth > element.clientWidth) { 318 | element.style.width = origWidth - padLeftAndRight + "px"; 319 | if (element.clientWidth < origWidth) { 320 | element.style.width = origWidth + borderLeftAndRight + "px"; 321 | } 322 | } 323 | } 324 | _pushReversed(allChars, charArray); 325 | if (words) { 326 | _pushReversed(allWords, wordArray); 327 | } 328 | _pushReversed(allLines, lineArray); 329 | }, 330 | _splitRawText = (element, vars, wordStart, charStart) => { 331 | let tag = vars.tag ? vars.tag : vars.span ? "span" : "div", 332 | types = vars.type || vars.split || "chars,words,lines", 333 | chars = ~types.indexOf("chars"), 334 | absolute = _isAbsolute(vars), 335 | wordDelimiter = vars.wordDelimiter || " ", 336 | space = wordDelimiter !== " " ? "" : absolute ? "­ " : " ", 337 | wordEnd = "", 338 | wordIsOpen = 1, 339 | specialChars = vars.specialChars ? (typeof vars.specialChars === "function" ? vars.specialChars : _findSpecialChars) : null, 340 | text, 341 | splitText, 342 | i, 343 | j, 344 | l, 345 | character, 346 | hasTagStart, 347 | testResult, 348 | container = _document.createElement("div"), 349 | parent = element.parentNode; 350 | 351 | parent.insertBefore(container, element); 352 | container.textContent = element.nodeValue; 353 | parent.removeChild(element); 354 | element = container; 355 | text = getText(element); 356 | hasTagStart = text.indexOf("<") !== -1; 357 | 358 | if (vars.reduceWhiteSpace !== false) { 359 | text = text.replace(_multipleSpacesExp, " ").replace(_stripExp, ""); 360 | } 361 | if (hasTagStart) { 362 | text = text.split("<").join("{{LT}}"); 363 | } 364 | l = text.length; 365 | splitText = (text.charAt(0) === " " ? space : "") + wordStart(); 366 | for (i = 0; i < l; i++) { 367 | character = text.charAt(i); 368 | if (specialChars && (testResult = specialChars(text.substr(i), vars.specialChars))) { 369 | character = text.substr(i, testResult || 1); 370 | splitText += chars && character !== " " ? charStart() + character + "" : character; 371 | i += testResult - 1; 372 | } else if (character === wordDelimiter && text.charAt(i - 1) !== wordDelimiter && i) { 373 | splitText += wordIsOpen ? wordEnd : ""; 374 | wordIsOpen = 0; 375 | while (text.charAt(i + 1) === wordDelimiter) { 376 | splitText += space; 377 | i++; 378 | } 379 | if (i === l - 1) { 380 | splitText += space; 381 | } else if (text.charAt(i + 1) !== ")") { 382 | splitText += space + wordStart(); 383 | wordIsOpen = 1; 384 | } 385 | } else if (character === "{" && text.substr(i, 6) === "{{LT}}") { 386 | splitText += chars ? charStart() + "{{LT}}" + "" : "{{LT}}"; 387 | i += 5; 388 | } else if ( 389 | (character.charCodeAt(0) >= 0xd800 && character.charCodeAt(0) <= 0xdbff) || 390 | (text.charCodeAt(i + 1) >= 0xfe00 && text.charCodeAt(i + 1) <= 0xfe0f) 391 | ) { 392 | j = ((text.substr(i, 12).split(emojiExp) || [])[1] || "").length || 2; 393 | splitText += chars && character !== " " ? charStart() + text.substr(i, j) + "" : text.substr(i, j); 394 | i += j - 1; 395 | } else { 396 | splitText += chars && character !== " " ? charStart() + character + "" : character; 397 | } 398 | } 399 | element.outerHTML = splitText + (wordIsOpen ? wordEnd : ""); 400 | if (hasTagStart) { 401 | _swapText(parent, "{{LT}}", "<"); 402 | } 403 | }, 404 | _split = (element, vars, wordStart, charStart) => { 405 | let children = _toArray(element.childNodes), 406 | l = children.length, 407 | absolute = _isAbsolute(vars), 408 | i, 409 | child; 410 | if (element.nodeType !== 3 || l > 1) { 411 | vars.absolute = false; 412 | for (i = 0; i < l; i++) { 413 | child = children[i]; 414 | if (child.nodeType !== 3 || /\S+/.test(child.nodeValue)) { 415 | if (absolute && child.nodeType !== 3 && _getComputedStyle(child).display === "inline") { 416 | child.style.display = "inline-block"; 417 | child.style.position = "relative"; 418 | } 419 | child._isSplit = true; 420 | _split(child, vars, wordStart, charStart); 421 | } 422 | } 423 | vars.absolute = absolute; 424 | element._isSplit = true; 425 | return; 426 | } 427 | _splitRawText(element, vars, wordStart, charStart); 428 | }; 429 | 430 | export class Splitter { 431 | constructor(element, vars) { 432 | if (!_coreInitted) { 433 | _initCore(); 434 | } 435 | this.elements = _toArray(element); 436 | this.chars = []; 437 | this.words = []; 438 | this.lines = []; 439 | this._originals = []; 440 | this.vars = vars || {}; 441 | if (_bonusValidated) { 442 | this._split(vars); 443 | } 444 | } 445 | 446 | _split(vars) { 447 | if (this.isSplit) { 448 | this.revert(); 449 | } 450 | this.vars = vars = vars || this.vars; 451 | this._originals.length = this.chars.length = this.words.length = this.lines.length = 0; 452 | let i = this.elements.length, 453 | tag = vars.tag ? vars.tag : vars.span ? "span" : "div", 454 | wordStart = cssClass(vars.wordsClass, tag), 455 | charStart = cssClass(vars.charsClass, tag), 456 | origHeight, 457 | origWidth, 458 | e; 459 | while (--i > -1) { 460 | e = this.elements[i]; 461 | this._originals[i] = e.innerHTML; 462 | origHeight = e.clientHeight; 463 | origWidth = e.clientWidth; 464 | _split(e, vars, wordStart, charStart); 465 | _setPositionsAfterSplit(e, vars, this.chars, this.words, this.lines, origWidth, origHeight); 466 | } 467 | this.chars.reverse(); 468 | this.words.reverse(); 469 | this.lines.reverse(); 470 | this.isSplit = true; 471 | return this; 472 | } 473 | 474 | revert() { 475 | let originals = this._originals; 476 | if (!originals) { 477 | throw "revert() call wasn't scoped properly."; 478 | } 479 | this.elements.forEach((e, i) => (e.innerHTML = originals[i])); 480 | this.chars = []; 481 | this.words = []; 482 | this.lines = []; 483 | this.isSplit = false; 484 | return this; 485 | } 486 | 487 | static create(element, vars) { 488 | return new Splitter(element, vars); 489 | } 490 | } 491 | 492 | export { Splitter as default }; 493 | -------------------------------------------------------------------------------- /src/Utils/dom.js: -------------------------------------------------------------------------------- 1 | export function mapEach(element, callback) { 2 | if (element instanceof window.HTMLElement) return [callback(element)]; 3 | if (typeof element === "object") { 4 | return Object.keys(element).map((key) => callback(element[key])); 5 | } else { 6 | return element.map(callback); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/Utils/isBrowser.js: -------------------------------------------------------------------------------- 1 | const isBrowser = 2 | typeof window !== "undefined" && 3 | typeof document !== "undefined" && 4 | typeof navigator !== "undefined" && 5 | typeof requestAnimationFrame !== "undefined"; 6 | 7 | export { isBrowser }; 8 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-prototype-builtins */ 2 | import Texts from "./types/Texts"; 3 | import { Config } from "./utils/Config"; 4 | import { isBrowser } from "./utils/isBrowser"; 5 | import { mapEach } from "./utils/Dom"; 6 | 7 | // Deep merge two objects. 8 | // @param target Object - The target object. 9 | // @param source Object - The source object. 10 | // @returns Object - The merged object. 11 | function deepMerge(target, source) { 12 | if (typeof target !== "object" || typeof source !== "object") { 13 | return source; 14 | } 15 | const merged = { ...target }; 16 | for (const key in source) { 17 | if (source.hasOwnProperty(key)) { 18 | if (merged.hasOwnProperty(key)) { 19 | merged[key] = deepMerge(merged[key], source[key]); 20 | } else { 21 | merged[key] = source[key]; 22 | } 23 | } 24 | } 25 | return merged; 26 | } 27 | 28 | // Textify 29 | // @param options Object - The options object. 30 | // @returns Object - The Textify instance. 31 | class Textify { 32 | /** 33 | * @constructor 34 | * @param {Object} options 35 | * @param {Object} engine 36 | * **/ 37 | constructor(options, engine) { 38 | this.DEFAULT_ELEMENT = options.el || "[data-textify]"; 39 | this.options = options; 40 | this.config = Config; 41 | this.engine = engine; 42 | 43 | // if engine is not defined, throw an error 44 | if (!this.engine) { 45 | throw new Error("engine is not defined. Please import a gsap. See https://greensock.com/docs/v3/Installation for more info."); 46 | } 47 | 48 | // Merge the options with the default config. 49 | this.controls = deepMerge(this.config, this.options); 50 | 51 | // Check if the browser is supported. 52 | if (isBrowser) { 53 | if (!document.querySelector(this.DEFAULT_ELEMENT)) { 54 | throw new Error(`Textify: Element "${this.DEFAULT_ELEMENT}" is not found.`); 55 | } 56 | this.TARGETS = [...document.querySelectorAll(this.DEFAULT_ELEMENT)]; 57 | this.ANIMATIONS = mapEach(this.TARGETS, (element) => { 58 | return new Texts({ 59 | element: element, 60 | controls: this.controls, 61 | engine: this.engine 62 | }); 63 | }); 64 | } 65 | } 66 | 67 | animateIn() { 68 | !isBrowser && console.log("Textify is not supported in this environment."); 69 | this.ANIMATIONS.forEach((animation) => { 70 | animation.animateIn(); 71 | }); 72 | } 73 | 74 | animateOut() { 75 | !isBrowser && console.log("Textify is not supported in this environment."); 76 | this.ANIMATIONS.forEach((animation) => { 77 | animation.animateOut(); 78 | }); 79 | } 80 | 81 | reset() { 82 | !isBrowser && console.log("Textify is not supported in this environment."); 83 | this.ANIMATIONS.forEach((animation) => { 84 | animation.reset(); 85 | }); 86 | } 87 | } 88 | 89 | export default Textify; 90 | 91 | // make Textify global 92 | if (isBrowser) { 93 | window.Textify = Textify; 94 | } else { 95 | global.Textify = Textify; 96 | console.log("Textify is not supported in this environment."); 97 | } 98 | -------------------------------------------------------------------------------- /src/types/Texts.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import Splitter from "../utils/Text"; 3 | import { isBrowser } from "../utils/isBrowser"; 4 | 5 | // Texts class 6 | // this class is responsible for splitting the text and animating it 7 | // it also creates an observer to animate the text when it is in the viewport 8 | // if the browser does not support IntersectionObserver, the text will be animated immediately 9 | // @param {element} element - the element to animate 10 | // @param {controls} controls - the controls object 11 | export default class Texts { 12 | /** 13 | * @constructor 14 | * @param {element} element - the element to animate 15 | * @param {controls} controls - the controls object 16 | * @param {engine} engine - the engine object 17 | * **/ 18 | constructor({ element, controls, engine }) { 19 | this.element = element; 20 | this.controls = controls; 21 | this.engine = engine; 22 | 23 | this.observer = null; 24 | this.text = null; 25 | this.textParts = {}; 26 | this.animatedElements = []; 27 | 28 | this.isError = false; 29 | 30 | this.threshold = this.controls.observer.threshold; 31 | this.repeat = this.controls.observer.repeat; 32 | this.animation = this.controls.animation; 33 | this.customAnimation = this.controls.animation.customAnimation; 34 | 35 | // _split 36 | this._split(); 37 | 38 | // createObserver 39 | if (isBrowser && "IntersectionObserver" in window) { 40 | this.createObserver(); 41 | this.customAnimation ? null : this.reset(); 42 | } else { 43 | this.customAnimation ? null : this.animateIn(); 44 | } 45 | } 46 | 47 | _split() { 48 | // if largeText is true, split the text by lines and throw an error 49 | if (this.controls.largeText) { 50 | if (this.controls.splitType !== "lines") { 51 | this.isError = true; 52 | throw Error("Textify: Large text must be split by lines only. Please set splitType to 'lines'"); 53 | } 54 | 55 | if (this.animation.by !== "lines") { 56 | this.isError = true; 57 | throw Error("Textify: Large text must be animated by lines only. Please set animation.by to 'lines'"); 58 | } 59 | } 60 | 61 | this.text = new Splitter(this.element, { 62 | type: this.controls.splitType, 63 | charsClass: this.controls.charsClass, 64 | wordsClass: this.controls.wordsClass, 65 | linesClass: this.controls.linesClass, 66 | position: this.controls.position, 67 | tag: this.controls.tag 68 | }); 69 | this.textParts = { 70 | chars: this.text.chars, 71 | words: this.text.words, 72 | lines: this.text.lines 73 | }; 74 | 75 | if (this.animation.by === "chars" && this.textParts.chars.length > 0) { 76 | this.animatedElements = this.textParts.chars; 77 | this.animatedElements.forEach((el, index) => { 78 | el.setAttribute("data-char", el.textContent); 79 | el.setAttribute("data-char-index", index); 80 | }); 81 | } 82 | if (this.animation.by === "words" && this.textParts.words.length > 0) { 83 | this.animatedElements = this.textParts.words; 84 | this.animatedElements.forEach((el, index) => { 85 | el.setAttribute("data-word", el.textContent); 86 | el.setAttribute("data-word-index", index); 87 | }); 88 | } 89 | if (this.animation.by === "lines" && this.textParts.lines.length > 0) { 90 | this.animatedElements = this.textParts.lines; 91 | this.animatedElements.forEach((el, index) => { 92 | el.setAttribute("data-line", el.textContent); 93 | el.setAttribute("data-line-index", index); 94 | }); 95 | } 96 | 97 | if (this.controls.largeText) { 98 | if (this.textParts.lines.length === 0) { 99 | this.isError = true; 100 | throw Error("Textify: Large text must have at least one line"); 101 | } 102 | 103 | this.textParts.lines.forEach((line) => { 104 | const div = document.createElement("div"); 105 | div.appendChild(line); 106 | this.element.appendChild(div); 107 | div.classList.add("line-box"); 108 | }); 109 | } 110 | 111 | this.element.classList.add("textify"); 112 | } 113 | 114 | createObserver() { 115 | this.observer = new window.IntersectionObserver((entries) => { 116 | entries.forEach((entry) => { 117 | if (entry.isIntersecting) { 118 | entry.target.classList.add("is-animated"); 119 | this.customAnimation ? null : this.animateIn(); 120 | this.repeat ? null : this.observer.unobserve(entry.target); 121 | } else { 122 | this.customAnimation ? null : this.reset(); 123 | this.repeat ? entry.target.classList.remove("is-animated") : null; 124 | } 125 | }); 126 | }); 127 | this.observer.observe(this.element); 128 | } 129 | 130 | animateIn() { 131 | if (this.isError) return; 132 | if (this.animation.customAnimation) { 133 | return this.element.classList.add("textify-custom-animation"); 134 | } 135 | 136 | this.engine.to(this.animatedElements, { 137 | opacity: 1, 138 | y: 0, 139 | x: 0, 140 | scale: 1, 141 | rotate: 0, 142 | skewX: 0, 143 | skewY: 0, 144 | duration: this.animation.duration, 145 | stagger: this.animation.stagger, 146 | delay: this.animation.delay, 147 | ease: this.animation.ease 148 | }); 149 | } 150 | 151 | animateOut() { 152 | if (this.isError) return; 153 | if (this.animation.customAnimation) { 154 | return this.element.classList.remove("textify-custom-animation"); 155 | } 156 | 157 | this.engine.to(this.animatedElements, { 158 | duration: this.animation.duration, 159 | stagger: this.animation.stagger, 160 | transformOrigin: this.animation.transformOrigin, 161 | opacity: this.animation.animateProps.opacity, 162 | y: this.animation.animateProps.y, 163 | x: this.animation.animateProps.x, 164 | scale: this.animation.animateProps.scale, 165 | rotate: this.animation.animateProps.rotate, 166 | skewX: this.animation.animateProps.skewX, 167 | skewY: this.animation.animateProps.skewY, 168 | ease: this.animation.ease 169 | }); 170 | } 171 | 172 | reset() { 173 | if (this.isError) return; 174 | if (this.animation.customAnimation) { 175 | return this.element.classList.remove("textify-custom-animation"); 176 | } 177 | 178 | this.engine.set(this.animatedElements, { 179 | duration: 0.1, // for better performance and to avoid flickering 180 | stagger: 0, // for better performance and to avoid flickering 181 | transformOrigin: this.animation.transformOrigin, 182 | opacity: this.animation.animateProps.opacity, 183 | y: this.animation.animateProps.y, 184 | x: this.animation.animateProps.x, 185 | scale: this.animation.animateProps.scale, 186 | rotate: this.animation.animateProps.rotate, 187 | skewX: this.animation.animateProps.skewX, 188 | skewY: this.animation.animateProps.skewY, 189 | ease: "none" // for better performance and to avoid flickering 190 | }); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /style/Textify.css: -------------------------------------------------------------------------------- 1 | /* fix safari custom kerning by adding a space after each character */ 2 | * { 3 | -webkit-font-feature-settings: "kern" 1; 4 | -moz-font-feature-settings: "kern" 1; 5 | -ms-font-feature-settings: "kern" 1; 6 | -o-font-feature-settings: "kern" 1; 7 | font-feature-settings: "kern" 1; 8 | font-kerning: none; 9 | -webkit-text-rendering: optimizeSpeed; 10 | text-rendering: optimizeSpeed; 11 | -webkit-transform: translateZ(0); 12 | transform: translateZ(0); 13 | } 14 | 15 | /* ----------------------------------------- */ 16 | .textify .word, 17 | .textify .char { 18 | display: inline-block; 19 | will-change: transform, opacity; /* for safari */ 20 | } 21 | 22 | /* ----------------------------------------- */ 23 | 24 | .textify .char { 25 | position: relative; 26 | } 27 | 28 | /** 29 | * Populate the psuedo elements with the character to allow for expanded effects 30 | * Set to `display: none` by default; just add `display: block` when you want 31 | * to use the psuedo elements 32 | */ 33 | .textify .char::before, 34 | .textify .char::after, 35 | .textify .word::after, 36 | .textify .word::before, 37 | .textify .line::after, 38 | .textify .line::before { 39 | position: absolute; 40 | top: 0; 41 | left: 0; 42 | visibility: hidden; 43 | transition: inherit; 44 | display: none; 45 | } 46 | 47 | .textify .char::before, 48 | .textify .char::after { 49 | content: attr(data-char); 50 | } 51 | 52 | .textify .word::after, 53 | .textify .word::before { 54 | content: attr(data-word); 55 | } 56 | 57 | .textify .line::after, 58 | .textify .line::before { 59 | content: attr(data-line); 60 | } 61 | 62 | /* ------------------------------------------- */ 63 | 64 | .textify .line-box, 65 | .textify .line { 66 | overflow: hidden; 67 | } 68 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const copyWebpackPlugin = require("copy-webpack-plugin"); 3 | const TerserPlugin = require("terser-webpack-plugin"); 4 | 5 | module.exports = { 6 | mode: "production", 7 | entry: "./src/index.js", 8 | output: { 9 | path: path.resolve(__dirname, "dist"), 10 | filename: "Textify.min.js", 11 | clean: true, 12 | library: { 13 | name: "Textify.js", 14 | type: "umd", 15 | export: "default" 16 | }, 17 | globalObject: "this", 18 | environment: { 19 | arrowFunction: false 20 | } 21 | }, 22 | module: { 23 | rules: [ 24 | { 25 | test: /\.js$/, 26 | exclude: /node_modules/, 27 | use: { 28 | loader: "babel-loader", 29 | options: { 30 | presets: ["@babel/preset-env"], 31 | plugins: ["@babel/plugin-transform-runtime"] 32 | } 33 | } 34 | }, 35 | { 36 | test: /\.css$/i, 37 | use: ["style-loader", "css-loader"] 38 | } 39 | ] 40 | }, 41 | plugins: [ 42 | new copyWebpackPlugin({ 43 | patterns: [{ from: "./style/Textify.css", to: "Textify.min.css" }] 44 | }) 45 | ], 46 | optimization: { 47 | minimize: true, 48 | minimizer: [ 49 | new TerserPlugin({ 50 | test: /\.js(\?.*)?$/i, 51 | extractComments: "all", 52 | minify: TerserPlugin.uglifyJsMinify 53 | }) 54 | ] 55 | } 56 | }; 57 | --------------------------------------------------------------------------------