├── .cursorignore ├── .eslintcache ├── .eslintrc.cjs ├── .github └── workflows │ └── algolia.yml ├── .gitignore ├── .npmignore ├── .prettierrc ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── README.us.md ├── api-extractor.json ├── dist ├── tsdoc-metadata.json ├── web-loading.cjs.js ├── web-loading.cjs.min.js ├── web-loading.d.ts ├── web-loading.esm-bundler.js ├── web-loading.js └── web-loading.min.js ├── index.html ├── index.js ├── package-lock.json ├── package.json ├── rollup.config.js ├── src ├── ExtendLoading │ └── index.ts ├── Webloading │ ├── index.ts │ ├── init.ts │ ├── style.ts │ └── type.ts ├── draw │ ├── index.ts │ ├── model │ │ ├── BaseModel.ts │ │ ├── Bean.ts │ │ ├── Circular.ts │ │ ├── Clock.ts │ │ ├── Gear.ts │ │ ├── Img.ts │ │ ├── Pattern.ts │ │ ├── Ring.ts │ │ ├── Roll.ts │ │ ├── Skeleton.ts │ │ ├── Zoom.ts │ │ └── index.ts │ └── type.ts ├── index.ts ├── loading.ts ├── main.ts ├── type.ts └── utils.ts ├── tsconfig.base.json └── tsconfig.json /.cursorignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /lib 3 | /node_modules 4 | /coverage 5 | /.vscode 6 | /.idea 7 | .DS_Store 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* -------------------------------------------------------------------------------- /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"F:\\tem-pro\\web-loading\\src\\draw\\index.ts":"1","F:\\tem-pro\\web-loading\\src\\draw\\model\\BaseModel.ts":"2","F:\\tem-pro\\web-loading\\src\\draw\\model\\Bean.ts":"3","F:\\tem-pro\\web-loading\\src\\draw\\model\\Circular.ts":"4","F:\\tem-pro\\web-loading\\src\\draw\\model\\Clock.ts":"5","F:\\tem-pro\\web-loading\\src\\draw\\model\\Gear.ts":"6","F:\\tem-pro\\web-loading\\src\\draw\\model\\Img.ts":"7","F:\\tem-pro\\web-loading\\src\\draw\\model\\index.ts":"8","F:\\tem-pro\\web-loading\\src\\draw\\model\\Pattern.ts":"9","F:\\tem-pro\\web-loading\\src\\draw\\model\\Ring.ts":"10","F:\\tem-pro\\web-loading\\src\\draw\\model\\Roll.ts":"11","F:\\tem-pro\\web-loading\\src\\draw\\model\\Skeleton.ts":"12","F:\\tem-pro\\web-loading\\src\\draw\\model\\Zoom.ts":"13","F:\\tem-pro\\web-loading\\src\\draw\\type.ts":"14","F:\\tem-pro\\web-loading\\src\\ExtendLoading\\index.ts":"15","F:\\tem-pro\\web-loading\\src\\index.ts":"16","F:\\tem-pro\\web-loading\\src\\loading.ts":"17","F:\\tem-pro\\web-loading\\src\\main.ts":"18","F:\\tem-pro\\web-loading\\src\\type.ts":"19","F:\\tem-pro\\web-loading\\src\\utils.ts":"20","F:\\tem-pro\\web-loading\\src\\Webloading\\index.ts":"21","F:\\tem-pro\\web-loading\\src\\Webloading\\init.ts":"22","F:\\tem-pro\\web-loading\\src\\Webloading\\style.ts":"23","F:\\tem-pro\\web-loading\\src\\Webloading\\type.ts":"24"},{"size":1239,"mtime":1745548174583,"results":"25","hashOfConfig":"26"},{"size":7988,"mtime":1745548412816,"results":"27","hashOfConfig":"26"},{"size":4565,"mtime":1745546611103,"results":"28","hashOfConfig":"26"},{"size":3816,"mtime":1745546611104,"results":"29","hashOfConfig":"26"},{"size":4610,"mtime":1745546611104,"results":"30","hashOfConfig":"26"},{"size":3418,"mtime":1745546611104,"results":"31","hashOfConfig":"26"},{"size":1716,"mtime":1745546611106,"results":"32","hashOfConfig":"26"},{"size":496,"mtime":1745546611107,"results":"33","hashOfConfig":"26"},{"size":8228,"mtime":1745546611106,"results":"34","hashOfConfig":"26"},{"size":3172,"mtime":1745546611107,"results":"35","hashOfConfig":"26"},{"size":6921,"mtime":1745546611107,"results":"36","hashOfConfig":"26"},{"size":4121,"mtime":1745546611107,"results":"37","hashOfConfig":"26"},{"size":4476,"mtime":1745546611107,"results":"38","hashOfConfig":"26"},{"size":4185,"mtime":1745546611108,"results":"39","hashOfConfig":"26"},{"size":1866,"mtime":1745546611101,"results":"40","hashOfConfig":"26"},{"size":562,"mtime":1745546611108,"results":"41","hashOfConfig":"26"},{"size":3909,"mtime":1745546611108,"results":"42","hashOfConfig":"26"},{"size":881,"mtime":1745546611108,"results":"43","hashOfConfig":"26"},{"size":2909,"mtime":1745547935920,"results":"44","hashOfConfig":"26"},{"size":6065,"mtime":1745547794768,"results":"45","hashOfConfig":"26"},{"size":5087,"mtime":1745547794766,"results":"46","hashOfConfig":"26"},{"size":1928,"mtime":1745547794766,"results":"47","hashOfConfig":"26"},{"size":3412,"mtime":1745548126713,"results":"48","hashOfConfig":"26"},{"size":238,"mtime":1745547794766,"results":"49","hashOfConfig":"26"},{"filePath":"50","messages":"51","suppressedMessages":"52","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"1joz206",{"filePath":"53","messages":"54","suppressedMessages":"55","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"56","messages":"57","suppressedMessages":"58","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"59","messages":"60","suppressedMessages":"61","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"62","messages":"63","suppressedMessages":"64","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"65","messages":"66","suppressedMessages":"67","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"68","messages":"69","suppressedMessages":"70","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"71","messages":"72","suppressedMessages":"73","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"74","messages":"75","suppressedMessages":"76","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"77","messages":"78","suppressedMessages":"79","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"80","messages":"81","suppressedMessages":"82","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"83","messages":"84","suppressedMessages":"85","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"86","messages":"87","suppressedMessages":"88","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"89","messages":"90","suppressedMessages":"91","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"92","messages":"93","suppressedMessages":"94","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"95","messages":"96","suppressedMessages":"97","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"98","messages":"99","suppressedMessages":"100","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"101","messages":"102","suppressedMessages":"103","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"104","messages":"105","suppressedMessages":"106","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"107","messages":"108","suppressedMessages":"109","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"110","messages":"111","suppressedMessages":"112","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"113","messages":"114","suppressedMessages":"115","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"116","messages":"117","suppressedMessages":"118","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"119","messages":"120","suppressedMessages":"121","errorCount":0,"fatalErrorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"F:\\tem-pro\\web-loading\\src\\draw\\index.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\BaseModel.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Bean.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Circular.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Clock.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Gear.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Img.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\index.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Pattern.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Ring.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Roll.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Skeleton.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\model\\Zoom.ts",[],[],"F:\\tem-pro\\web-loading\\src\\draw\\type.ts",[],[],"F:\\tem-pro\\web-loading\\src\\ExtendLoading\\index.ts",[],[],"F:\\tem-pro\\web-loading\\src\\index.ts",[],[],"F:\\tem-pro\\web-loading\\src\\loading.ts",[],[],"F:\\tem-pro\\web-loading\\src\\main.ts",[],[],"F:\\tem-pro\\web-loading\\src\\type.ts",[],[],"F:\\tem-pro\\web-loading\\src\\utils.ts",[],[],"F:\\tem-pro\\web-loading\\src\\Webloading\\index.ts",[],[],"F:\\tem-pro\\web-loading\\src\\Webloading\\init.ts",[],[],"F:\\tem-pro\\web-loading\\src\\Webloading\\style.ts",[],[],"F:\\tem-pro\\web-loading\\src\\Webloading\\type.ts",[],[]] -------------------------------------------------------------------------------- /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true, 5 | node: true, 6 | es6: true, 7 | }, 8 | parserOptions: { 9 | parser: "@typescript-eslint/parser", 10 | ecmaVersion: 2020, 11 | sourceType: "module", 12 | ecmaFeatures: { 13 | jsx: true, 14 | }, 15 | }, 16 | extends: ["plugin:@typescript-eslint/recommended", "plugin:prettier/recommended"], 17 | rules: { 18 | "@typescript-eslint/ban-ts-ignore": "off", 19 | "@typescript-eslint/explicit-function-return-type": "off", 20 | "@typescript-eslint/no-explicit-any": "off", 21 | "@typescript-eslint/no-var-requires": "off", 22 | "@typescript-eslint/no-empty-function": "off", 23 | "no-use-before-define": "off", 24 | "@typescript-eslint/no-use-before-define": "off", 25 | "@typescript-eslint/ban-ts-comment": "off", 26 | "@typescript-eslint/ban-types": "off", 27 | "@typescript-eslint/no-non-null-assertion": "off", 28 | "@typescript-eslint/explicit-module-boundary-types": "off", 29 | "@typescript-eslint/no-unused-vars": [ 30 | "error", 31 | { 32 | argsIgnorePattern: "^_", 33 | varsIgnorePattern: "^_", 34 | }, 35 | ], 36 | "no-unused-vars": [ 37 | "error", 38 | { 39 | argsIgnorePattern: "^_", 40 | varsIgnorePattern: "^_", 41 | }, 42 | ], 43 | "space-before-function-paren": "off", 44 | }, 45 | }; 46 | -------------------------------------------------------------------------------- /.github/workflows/algolia.yml: -------------------------------------------------------------------------------- 1 | name: algolia 2 | on: 3 | push: 4 | branches: 5 | - master 6 | jobs: 7 | algolia: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: actions/checkout@v3 11 | - name: Get the content of algolia.json as config 12 | id: algolia_config 13 | run: echo "config=$(cat crawlerConfig.json | jq -r tostring)" >> $GITHUB_OUTPUT 14 | - name: Push indices to Algolia 15 | uses: signcl/docsearch-scraper-action@master 16 | env: 17 | APPLICATION_ID: ${{ secrets.APPLICATION_ID }} 18 | API_KEY: ${{ secrets.API_KEY }} 19 | CONFIG: ${{ steps.algolia_config.outputs.config }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | lib 3 | temp 4 | docs -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | src 2 | temp 3 | lib 4 | .eslint* 5 | docs 6 | api-extractor.json 7 | rollup.config* 8 | tsconfig* 9 | package-lock.json -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "eslintIntegration": true, 3 | "stylelintIntegration": true, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "semi": false, 7 | "trailingComma": "none", 8 | "htmlWhitespaceSensitivity": "ignore", 9 | "printWidth": 120, 10 | "endOfLine": "auto" 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "commentTranslate.source": "DarkCWK.youdao-youdao" 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # web-loading 2 | 3 |

4 | web-loading logo 5 |

6 | 7 |

8 | 基于 Canvas 的高性能 Web 加载动画库,为您的应用提供丝滑加载效果 9 |

10 | 11 |

12 | 13 | license 14 | 15 | 16 | npm 17 | 18 | 19 | stars 20 | 21 |

22 | 23 | ## 🔥 特性 24 | 25 | - 💪 基于 Canvas 绘制,性能优异 26 | - 🔥 使用 TypeScript 编写,提供完整的类型定义 27 | - 📦 开箱即用的多种加载动画效果 28 | - 🌈 支持三种挂载模式:DOM、全屏、移动端 29 | - 👓 提供完整的生命周期钩子函数 30 | - ⚡ 支持自定义动画模型 31 | 32 | ## 📋 更新日志 33 | 34 | - 优化源码文件结构 35 | - 新增无感刷新功能 36 | - 解决秒关闪屏bug 37 | - 更换打包方式,引入路径有变化 38 | 39 | ## 🎬 官方文档 40 | 41 | [查看官方文档和示例](https://tommyrunner.github.io/web-loading/) 42 | 43 | ## 介绍 44 | 45 | Web 中实现 loading 的方式有很多种,例如使用`css`动画、`js`操作元素、`gif`图片、`svg`动画、`ui`框架中自带`loading`等等,各有所优,操作元素可能更方便,但会影响性能或其他元素,动态图片性能很好,但自定义不理想。 46 | 47 | `WebLoading` 是一个基于 web 封装的`loading`动画插件,主要**model**是通过`Canvas`绘制,这种方式不会影响到界面中的元素,当然,`WebLoading`也提供了`html`配置兼容了**html**加载方式。默认的**model**模块都提供了独自的`options`配置属性,如果想更贴近业务可以使用`Custom`进行自定义,`WebLoading`提供了`BaseModel` 继承`class`让你更方便自定义自己的`loading`,或者`html`加载方式。 48 | 49 | ## 实现 50 | 51 | `WebLoading`中每一个**model**都是使用`Canvas`绘制,启动方式分别有`DOM`(元素挂载)、`FULL`(全屏)、`MINI`(移动端)。 52 | 53 | 原理大同小异,这里以`DOM`来讲述,首先我们需要`initLoading`初始化你需要渲染的**model**并提供自定义参数,当然,这个操作不是必须的,因为`WebLoading`已经初始化所有的默认数据,此时抛出操作`WebLoading`相关函数。 54 | 55 | 启动`WebLoading`调用`loading`函数需要一个`HtmlElement`元素,该元素必须拥有`children`,而不是一个单标签元素。启动`WebLoading`时会获取到这个挂载的元素,并在`children`添加一个`Canvas`,同时会计算该元素位置以及大小以最优显示同步到`Canvas`上。`WebLoading`会根据`options`参数来绘制具体的**model**,**model**中主要以`requestAnimationFrame`来进行递归回调渲染,以此来实现每一帧动画。 56 | 57 | 注意:如果配置是通过**html**渲染,那么就不会走上一步。 58 | 59 | `WebLoading`封装上主要分隔三层 60 | 61 | - 交互层:开发者与`WebLoading`的操作,例如初始化、启动、关闭、获取相关信息等等。 62 | - 逻辑层:获取到`WebLoading`接收`options`后进行初始化挂载的元素以及`canvas`等等。 63 | - model 层:继承`BaseModel`获取初始化后的`canvas`进行绘制模块。 64 | 65 | ## 安装 66 | 67 | > 根据自己的包管理工具下载。 68 | 69 | ```sh 70 | npm install web-loading 71 | ``` 72 | 73 | ## 使用 74 | 75 | ### CDN引入 76 | 77 | ```html 78 | 79 | 83 | ``` 84 | 85 | ### 工程项目引入 86 | 87 | ```typescript 88 | import type { LoadingType } from "web-loading"; 89 | import { initLoading } from "web-loading"; 90 | let webLoading: LoadingType = initLoading({ 91 | // 自定义options 92 | }) 93 | ``` 94 | 95 | > - 参数 96 | > - `options?:OptionsType` 97 | > - 返回 98 | > - `webLoading:LoadingType` 99 | 100 | ### 获取元素 101 | 102 | ```typescript 103 | // 无框架情况 104 | let dom = document.querySelector('xxx') 105 | // vue 106 | let dom = ref() 107 | // ...如果是FULL或MINI不需要获取元素 108 | ``` 109 | 110 | ### 启动 111 | 112 | ```typescript 113 | // 注意:在dom加载完成后再调用loading 114 | window.onload = function () { 115 | webLoading.loading(dom) 116 | } 117 | ``` 118 | 119 | > 参数 120 | > 121 | > - `dom`:挂载的`HtmlElement`元素 122 | > 123 | > - `options?:OptionsType`,支持覆盖`options`。 124 | 125 | ## 启动方式 126 | 127 | `DOM`、`FULL`、`MINI`三种启动方式都需要基于`HtmlElement`,这里`FULL`、`MINI`是扩展的启动方式,参数中无须提供`HtemlElment`,是因为`WebLoading`已经处理的元素的创建到消失的流程。 128 | 129 | ```typescript 130 | import type { LoadingType } from "web-loading"; 131 | import { fullLoading,miniLoading LOADING_TYPES } from "web-loading"; 132 | 133 | let webLoading: LoadingType = fullLoading() // 全屏 134 | // let webLoading: LoadingType = miniLoading() // 移动端 135 | 136 | // 启动(如果是MINI或者FULL无需传递dom) 137 | webLoading.loading() 138 | ``` 139 | 140 | -------------------------------------------------------------------------------- /README.us.md: -------------------------------------------------------------------------------- 1 | # web-loading 2 | 3 |

4 | web-loading logo 5 |

6 | 7 |

8 | High-performance Web loading animation library based on Canvas, providing smooth loading effects for your applications 9 |

10 | 11 |

12 | 13 | license 14 | 15 | 16 | npm 17 | 18 | 19 | stars 20 | 21 |

22 | 23 | ## 🔥 Features 24 | 25 | - 💪 Based on Canvas rendering, excellent performance 26 | - 🔥 Written in TypeScript, providing complete type definitions 27 | - 📦 Multiple out-of-the-box loading animation effects 28 | - 🌈 Supports three mounting modes: DOM, full-screen, and mobile 29 | - 👓 Provides complete lifecycle hook functions 30 | - ⚡ Supports custom animation models 31 | 32 | ## 📋 Changelog 33 | 34 | - Optimized source code structure 35 | - Added seamless refresh functionality 36 | - Fixed flash screen bug on quick close 37 | - Changed packaging method, import paths have changed 38 | 39 | ## 🎬 Official Documentation 40 | 41 | [View official documentation and examples](https://tommyrunner.github.io/web-loading/) 42 | 43 | 44 | ## introduce 45 | 46 | There are many ways to implement loading on the Web, such as using `css` animation,`js` operation element, `gif` image, `svg` animation, and `loading` in the `ui` framework. Each has its own advantages. The operation element may be more convenient, but it will affect performance or other elements. The dynamic image performance is good, but the customization is not ideal. 47 | 48 | `WebLoading`is a `loading`animation plug-in based on web encapsulation. The main **model** is drawn through` Canvas`. This method will not affect the elements in the interface. Of course, `WebLoading` also provides`html`configuration, which is compatible with the **html** loading method. The default **model** module provides its own `options` configuration attribute. If you want to be closer to the business, you can use`Custom`to customize it.`WebLoading`provides` BaseModel` inheritance `class` to make it easier for you to customize your own`loading`or`html`loading method. 49 | 50 | ## realization 51 | 52 | `Each **model** in WebLoading `is drawn using` Canvas`. The startup methods include `DOM` (element mount), `FULL` (full screen), and `MINI` (mobile terminal). 53 | 54 | The principle is very similar. For `DOM`, first we need `initLoading` to initialize the **model** that you need to render and provide custom parameters. Of course, this operation is not necessary, because `WebLoading` has initialized all the default data. At this time, the operation `WebLoading` related function is thrown. 55 | 56 | Starting the `WebLoading` and calling the `loading` function requires a `HtmlElement` element, which must have `children` instead of a single label element. When you start `WebLoading`, you will get the mounted element and add a `Canvas` in`children`. At the same time, the location and size of the element will be calculated and synchronized to` Canvas` for optimal display`WebLoading`will draw the specific **model** according to the` options` parameter, **In model** recursive callback rendering is mainly performed using `requestAnimationFrame` to achieve each frame of animation. 57 | 58 | Note: If the configuration is rendered through **html**, you will not go to the next step. 59 | 60 | `WebLoading ` package is mainly divided into three layers 61 | 62 | - Interaction layer: operations between developers and `WebLoading`, such as initialization, startup, shutdown, obtaining relevant information, etc. 63 | - Logical layer: get the elements to be initialized and mounted after `WebLoading` receives` options` and `canvas`. 64 | - Model layer: Inherit `BaseModel` to get the initialized `canvas` for drawing module. 65 | 66 | ## install 67 | 68 | > Download according to your own package management tool. 69 | 70 | ```sh 71 | npm install web-loading 72 | ``` 73 | 74 | ## use 75 | 76 | ### CDN introduction 77 | 78 | ```html 79 | 80 | 84 | ``` 85 | 86 | ### Introduction of engineering projects 87 | 88 | ```typescript 89 | import type { LoadingType } from "web-loading"; 90 | import { initLoading } from "web-loading"; 91 | let webLoading: LoadingType = initLoading({ 92 | // Custom options 93 | }) 94 | ``` 95 | 96 | > - parameter 97 | > - `options?:OptionsType` 98 | > - return 99 | > - `webLoading:LoadingType` 100 | 101 | ### Get Element 102 | 103 | ```typescript 104 | // No frame 105 | let dom = document.querySelector('xxx') 106 | // vue 107 | let dom = ref() 108 | // ...If it is FULL or MINI, it is not necessary to obtain the element 109 | ``` 110 | 111 | ### start-up 112 | 113 | ```typescript 114 | // Note: After the dom loading is completed, call loading 115 | window.onload = function () { 116 | webLoading.loading(dom) 117 | } 118 | ``` 119 | 120 | > parameter 121 | > 122 | > - `dom`:Mounted`HtmlElement`element 123 | > 124 | > - `options?:OptionsType`, Support Overrides`options`. 125 | 126 | ## Start mode 127 | 128 | `DOM `,`FULL`,`MINI`All three startup methods need to be based on`HtmlElement`, where`FULL`and`MINI`are the extended startup methods, and`HtmlElement`is not required in the parameters, because`WebLoading`has processed the process from creation to disappearance of elements. 129 | 130 | ```typescript 131 | import type { LoadingType } from "web-loading"; 132 | import { fullLoading,miniLoading LOADING_TYPES } from "web-loading"; 133 | 134 | let webLoading: LoadingType = fullLoading() // full screen 135 | // let webLoading: LoadingType = miniLoading() // mobile terminal 136 | 137 | // Start (if it is MINI or FULL, there is no need to pass dom) 138 | webLoading.loading() 139 | ``` 140 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "mainEntryPointFilePath": "/lib/index.d.ts", 5 | 6 | "bundledPackages": [], 7 | 8 | "compiler": { 9 | "tsconfigFilePath": "/tsconfig.json" 10 | }, 11 | 12 | "apiReport": { 13 | "enabled": false 14 | }, 15 | 16 | "docModel": { 17 | "enabled": true 18 | }, 19 | 20 | "dtsRollup": { 21 | "enabled": true, 22 | "untrimmedFilePath": "/dist/web-loading.d.ts" 23 | }, 24 | 25 | "tsdocMetadata": { 26 | "enabled": true, 27 | "tsdocMetadataFilePath": "/dist/tsdoc-metadata.json" 28 | }, 29 | 30 | "messages": { 31 | "compilerMessageReporting": { 32 | "default": { 33 | "logLevel": "warning" 34 | } 35 | }, 36 | 37 | "extractorMessageReporting": { 38 | "default": { 39 | "logLevel": "warning" 40 | }, 41 | "ae-forgotten-export": { 42 | "logLevel": "none" 43 | } 44 | }, 45 | 46 | "tsdocMessageReporting": { 47 | "default": { 48 | "logLevel": "warning" 49 | }, 50 | "tsdoc-undefined-tag": { 51 | "logLevel": "none" 52 | }, 53 | "tsdoc-param-tag-with-invalid-type": { 54 | "logLevel": "none" 55 | }, 56 | "tsdoc-param-tag-with-invalid-optional-name": { 57 | "logLevel": "none" 58 | }, 59 | "tsdoc-escape-right-brace": { 60 | "logLevel": "none" 61 | }, 62 | "tsdoc-malformed-inline-tag": { 63 | "logLevel": "none" 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /dist/tsdoc-metadata.json: -------------------------------------------------------------------------------- 1 | // This file is read by tools that parse documentation comments conforming to the TSDoc standard. 2 | // It should be published with your NPM package. It should not be tracked by Git. 3 | { 4 | "tsdocVersion": "0.12", 5 | "toolPackages": [ 6 | { 7 | "packageName": "@microsoft/api-extractor", 8 | "packageVersion": "7.35.2" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /dist/web-loading.cjs.min.js: -------------------------------------------------------------------------------- 1 | "use strict";var t,e,i,o;function n(){return{custom:null,html:"",type:exports.LOADING_TYPES.DOM,extendClass:"extend",model:exports.MODEL_TYPES.GEAR,text:"加载中...",textGap:8,fontSize:12,fontFamily:"Microsoft YaHei",delay:65,notFeel:0,delayInto:320,optimization:!1,zIndex:"2001",themeColor:"rgba(64,158,255,1)",bgColor:"rgba(0, 0, 0, 0.8)",shadowColor:"rgba(64,158,255,0.6)",shadowOffsetX:2,shadowOffsetY:2,shadowBlur:5,pointerEvents:!1,toast:!0}}Object.defineProperty(exports,"__esModule",{value:!0}),exports.LOADING_TYPES=void 0,(t=exports.LOADING_TYPES||(exports.LOADING_TYPES={})).DOM="dom",t.FULL="full",t.MINI="mini",exports.MODEL_TYPES=void 0,(e=exports.MODEL_TYPES||(exports.MODEL_TYPES={})).GEAR="Gear",e.RING="Ring",e.ZOOM="Zoom",e.PATTERN="Pattern",e.CLOCK="Clock",e.BEAN="Bean",e.ROLL="Roll",e.CIRCULAR="Circular",e.IMG="Img",e.SKELETON="Skeleton",exports.HOOKS_CALL_KEY=void 0,(i=exports.HOOKS_CALL_KEY||(exports.HOOKS_CALL_KEY={})).BEFORE_CLOSE="beforeClose",i.CLOSED="closed",exports.LOG_TYPES=void 0,(o=exports.LOG_TYPES||(exports.LOG_TYPES={}))[o.INFO=1]="INFO",o[o.WARN=2]="WARN",o[o.ERROR=3]="ERROR";var s,a,r,h,l=function(){function t(){}return t.info=function(t){this.call(t,exports.LOG_TYPES.INFO)},t.warn=function(t){this.call(t,exports.LOG_TYPES.WARN)},t.error=function(t){this.call(t,exports.LOG_TYPES.ERROR)},t.call=function(t,e,i){void 0===e&&(e=exports.LOG_TYPES.INFO),void 0===i&&(i={color:n().themeColor,bgColor:n().bgColor});var o=i.bgColor;2===e&&(o="#fffbe5"),3===e&&(o="#fff0f0");var s="\n background:".concat(o,";\n font-size:14px;\n color:").concat(i.color,";\n padding: 4px;\n border: 1px solid;");console.log("%c web-loading:".concat(t," "),s)},t}();function c(t){switch(d(t)){case"object":return Object.keys(t).length>0;case"array":return t.length>0;case"undefined":default:return void 0!==t;case"null":return null!==t}}function p(t){window.requestAnimationFrame?window.cancelAnimationFrame(t):window.clearInterval(t)}function d(t){try{return Object.prototype.toString.call(t).split(" ")[1].split("]")[0].toLowerCase()}catch(t){return"not-type"}}function x(t,e){var i=null,o={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(var n in o)if(void 0!==t.style[n]){i=o[n];break}if(i){var s=function(){e(),t.removeEventListener(i,s)};t.addEventListener(i,s)}else e()}function u(){var t=String(Date.now());return window.crypto&&window.crypto.randomUUID&&(t=window.crypto.randomUUID()),"wl_"+t.replace(/-/g,"")}exports.ZOOM_ACTION=void 0,(s=exports.ZOOM_ACTION||(exports.ZOOM_ACTION={})).SCALE="scale",s.WAVE="wave",s.HEIGHT="height",exports.PATTERN_CHART=void 0,(a=exports.PATTERN_CHART||(exports.PATTERN_CHART={})).RECT="rect",a.ARC="arc",a.TRIANGLE="triangle",a.HEART="heart",a.POLYGON="polygon",exports.ROLL_CHART=void 0,(r=exports.ROLL_CHART||(exports.ROLL_CHART={})).RECT="rect",r.WHEEL="wheel",r.WINDMILL="windmill",exports.CIRCULAR_ACTION=void 0,(h=exports.CIRCULAR_ACTION||(exports.CIRCULAR_ACTION={})).COLLISION="collision",h.ROTATE="rotate";var f=function(){function t(t,e,i,o,n,s,a,r){this.modelDefOptions=void 0,this.limits=void 0,this.modelDefCall=void 0,this.webLog=l,this.stepClear=1,this.w=t,this.h=e,this.canvas=i,this.ctx=i.getContext("2d"),this.options=o,this.element=n,this.initContextCall(s,a,r)}return t.prototype._$initBaseContext=function(){this.clearRect(),this.ctx.resetTransform();var t=this.options,e=this.canvas.width,i=this.canvas.height;this.ctx.fillStyle=t.themeColor,this.ctx.strokeStyle=t.themeColor,this.ctx.shadowColor=t.shadowColor,this.ctx.font="".concat(t.fontSize,"px ").concat(t.fontFamily," small-caps"),this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.translate(e/2,i/2);var o=window.devicePixelRatio||1;this.ctx.scale(o,o),this.ctx.save()},t.prototype._$initEvent=function(){var t=this;this.element.$store&&this.element.$store.hookCall.beforeClose((function(){t.clearRect()}))},t.prototype._$animationFrame=function(t){var e=this,i=this.element.$store;if(i){window.requestAnimationFrame||(i.animationId=window.setInterval(t,this.options.delay));var o=Date.now()+this.options.delay;t.call(this);var n=function(){Date.now()>o&&(t.call(e),o=Date.now()+e.options.delay),i.animationId=window.requestAnimationFrame(n)};i.animationId=window.requestAnimationFrame(n)}},t.prototype.initContextCall=function(t,e,i){var o=this;this._$initBaseContext(),this._$initEvent(),c(t)&&(this.modelDefOptions=t,this.options=Object.assign(t,this.options),this.element.$store&&(this.element.$store.options=this.options),e&&e.length&&this.options.toast&&e.forEach((function(t){var e=o.options[t.key];c(e)&&!t.limit(e)&&l.warn(t.message)}))),c(e)&&(this.limits=e),c(i)&&i.call(this,this)},t.prototype.run=function(t){var e=this.element.$store;e&&e.animationId&&this.clearAnimationFrame(e.animationId),this._$animationFrame(t)},t.prototype.clearAnimationFrame=function(t){p(t)},t.prototype.clearRect=function(t,e,i,o){var n=this.canvas.width,s=this.canvas.height;if(c(t)&&c(e)&&c(i)&&c(o))this.ctx.clearRect(t,e,i,o);else if(c(t)&&c(e)&&c(i)&&!c(o)){var a=i-this.stepClear,r=Math.sqrt(i*i-a*a),h=t-a,l=e-r,p=2*a,d=2*r;this.stepClear<=i?(this.ctx.clearRect(h,l,p,d),this.stepClear+=1,this.clearRect(t,e,i)):this.stepClear=1}else this.ctx.clearRect(-n,-s,2*n,2*s)},t.prototype.drawRadiusRect=function(t,e,i,o,n){this.ctx.beginPath(),this.ctx.arc(t+n,e+n,n,1*Math.PI,1.5*Math.PI),this.ctx.lineTo(t+i-n,e),this.ctx.arc(t+i-n,e+n,n,1.5*Math.PI,0),this.ctx.lineTo(t+i,e+o-n),this.ctx.arc(t+i-n,e+o-n,n,0,.5*Math.PI),this.ctx.lineTo(t+n,e+o),this.ctx.arc(t+n,e+o-n,n,.5*Math.PI,Math.PI),this.ctx.lineTo(t,e+n),this.ctx.closePath()},t.prototype.drawText=function(t){var e=this.options,i=Object.assign({esGap:e.textGap||0,x:0,text:e.text||"",textColor:e.themeColor||"rgba(64,158,255,1)"},t);this.ctx.save(),this.ctx.beginPath(),this.ctx.fillStyle=i.textColor,this.ctx.fillText(i.text,i.x,(e.textGap||0)+(e.fontSize||0)+i.esGap),this.ctx.closePath(),this.ctx.restore()},t}(),m=function(t,e){return m=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[i]=e[i])},m(t,e)};function w(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Class extends value "+String(e)+" is not a constructor or null");function i(){this.constructor=t}m(t,e),t.prototype=null===e?Object.create(e):(i.prototype=e.prototype,new i)}var v={lineStart:10,lineEnd:16,lineStartSkew:0,lineEndSkew:0,lineWidth:4,lineCap:"round",lineNum:10,direction:!0},g=[{key:"lineNum",message:"lineNum value 4-18",limit:function(t){return t>=4&&t<=18}}],S=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,v,g,(function(t){var e=t.options;t.ctx.lineCap=e.lineCap,t.ctx.lineWidth=e.lineWidth,t.ctx.save()}))||this;return a.optimization(a.options.textGap+a.options.lineEnd),a.aps=Array.from({length:a.options.lineNum},(function(t,e){return e})),a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){this.clearRect(),this.controller(),this.drawGear();var t=this.options;this.drawText({esGap:t.lineEnd})},e.prototype.controller=function(){var t=this;this.options.direction?this.aps=this.aps.map((function(e){return e-1<=0?t.aps.length-1:e-1})):this.aps=this.aps.map((function(e){return e+1>t.aps.length?0:e+1}))},e.prototype.drawGear=function(){var t=this.options;this.ctx.save(),this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur;for(var e=0;ethis.h&&(this.options.lineStart=this.h/6-5,this.options.lineEnd=this.h/6,this.options.textGap=2)},e}(f),C={zoomGap:10,maxSize:16,zoomNum:5,lineWidth:10,zoomHeight:2,lineCap:"round",action:exports.ZOOM_ACTION.SCALE,direction:!0,zoomColors:[]},T=[{key:"lineWidth",message:"lineWidth(default:10) <= maxSize(default:16)",limit:function(t){return t<=C.maxSize}},{key:"maxSize",message:"lineWidth(default:10) <= maxSize(default:16)",limit:function(t){return C.lineWidth<=t}}],y=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,C,T,(function(t){var e=t.options;t.ctx.lineCap=e.lineCap,t.ctx.lineWidth=e.lineWidth,t.ctx.translate(-(e.lineWidth*(e.zoomNum+1)+e.zoomGap*(e.zoomNum+1))/2,-e.zoomHeight/2),t.ctx.save()}))||this;return a.zoomIndex=a.options.direction?0:a.options.zoomNum-1,a.list=Array.from({length:a.options.zoomNum},(function(t,e){return Object.assign({value:a.options.lineWidth,state:0})})),a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){this.clearRect(),this.drawZoom();var t=this.options;this.drawText({esGap:t.maxSize,x:(t.lineWidth*(t.zoomNum+1)+t.zoomGap*(t.zoomNum+1))/2}),this.controller()},e.prototype.controller=function(){var t=this.options;t.direction&&this.zoomIndex>=t.zoomNum?this.zoomIndex=0:t.direction&&this.zoomIndex<0&&(this.zoomIndex=t.zoomNum-1)},e.prototype.drawZoom=function(){for(var t=this.options,e=0;e=t.lineWidth&&this.list[e].value--,t.action===exports.ZOOM_ACTION.SCALE&&(this.ctx.lineWidth=this.list[e].value),e===this.zoomIndex&&(this.list[e].value>t.maxSize&&(this.list[e].state=2,t.direction?this.zoomIndex++:this.zoomIndex-1>=0?this.zoomIndex--:this.zoomIndex=t.zoomNum-1),this.list[e].value<=t.lineWidth&&(this.list[e].state=1)),this.ctx.beginPath(),t.zoomColors.length>0&&t.zoomColors[e]?this.ctx.strokeStyle=t.zoomColors[e]:this.ctx.strokeStyle=t.themeColor;var i=0,o=t.zoomHeight;t.action!==exports.ZOOM_ACTION.HEIGHT&&t.action!==exports.ZOOM_ACTION.WAVE||(i=-this.list[e].value),t.action===exports.ZOOM_ACTION.WAVE&&(o=-this.list[e].value),this.ctx.moveTo((e+1)*(t.lineWidth+t.zoomGap),i),this.ctx.lineTo((e+1)*(t.lineWidth+t.zoomGap),o),this.ctx.stroke(),this.ctx.closePath()}},e}(f),O={arcGap:Math.PI/4,ringGap:10,lineWidth:2,ringNum:2,radius:6,lineCap:"round",turn:10,ringsTurn:[Math.PI,Math.PI/4],direction:!0},E=[{key:"ringNum",message:"ringNum value 1-10",limit:function(t){return t>=1&&t<=10}},{key:"ringsTurn",message:"ringsTurn size ".concat(O.ringNum),limit:function(t){return t.length<=O.ringNum}}],I=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,O,E,(function(t){var e=t.options;t.ctx.lineCap=e.lineCap,t.ctx.lineWidth=e.lineWidth,t.ctx.save()}))||this;return a.rotate=10,a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){this.clearRect(),this.controller();var t=this.options;this.drawText({esGap:t.ringNum*(t.radius+t.ringGap/2)})},e.prototype.controller=function(){this.ctx.save();var t=this.options,e=this.rotate*Math.PI/180*(t.direction?1:-1);this.ctx.rotate(e),this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur;for(var i=1;i<=t.ringNum;i++)this.drawRing(t.radius+(i-1)*t.ringGap,t.arcGap,t.ringsTurn&&t.ringsTurn.length>0?t.ringsTurn[i-1]:Math.PI/i);this.rotate+=t.turn,this.ctx.restore()},e.prototype.drawRing=function(t,e,i){void 0===e&&(e=1),void 0===i&&(i=0),this.ctx.beginPath(),this.ctx.arc(0,0,t,e+i,Math.PI+i),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t,Math.PI+e+i,i),this.ctx.stroke(),this.ctx.closePath()},e}(f),b={beanSize:15,pointLength:15},P=[{key:"pointLength",message:"pointLength value >= 5",limit:function(t){return t>=5}},{key:"beanSize",message:"beanSize value >= 5",limit:function(t){return t>=5}}],R=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,b,P)||this;return a.bean={turn:30,state:1,beanState:1,nowX:-a.options.pointLength*a.options.beanSize/2-3*a.options.beanSize,beans:Array.from({length:a.options.pointLength},(function(){return 1})),beanAnimaIndex:0},a.options.delay=10,a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){var t=this.options;this.clearRect(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(this.bean.nowX,0),this.ctx.arc(0,0,t.beanSize,(360-this.bean.turn)*Math.PI/180,this.bean.turn*Math.PI/180,!0),this.ctx.lineTo(0,0),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore(),this.drawPoint(),this.drawFilter(),this.drawText({esGap:t.beanSize}),this.controller()},e.prototype.controller=function(){var t=this.options;this.bean.nowX>=t.pointLength*t.beanSize/2+2*t.beanSize&&(this.bean.nowX=-t.pointLength*t.beanSize/2-3*t.beanSize,this.bean.beanAnimaIndex=0),this.bean.nowX<=-t.pointLength*t.beanSize/2&&(this.bean.beanState=2),this.bean.turn<=0&&(this.bean.state=2),this.bean.turn>=30&&(this.bean.state=1),1===this.bean.state&&(this.bean.turn-=1),2===this.bean.state&&(this.bean.turn+=1),1===this.bean.beanState&&(this.bean.nowX-=1),2===this.bean.beanState&&(this.bean.nowX+=1)},e.prototype.drawPoint=function(){var t=this.options;this.ctx.save(),this.setShadow(),this.ctx.translate(-t.pointLength*t.beanSize/2,0);for(var e=0;e=5&&t<=24}},{key:"delay",message:"Pattern.delay not allowed update",limit:function(t){return t===n().delay}}],N=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,k,_,(function(t){t.options.delay=10}))||this;return a.pattern={color:a.randomState("chartColors"),nowHeight:10,chart:a.randomState("charts"),shadow:0,nowState:1,turn:0},a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){var t=this.options;this.clearRect(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(0,this.pattern.nowHeight),this.ctx.rotate(this.pattern.turn/Math.PI*2),this.ctx.fillStyle=this.pattern.color,this.selectChart(0,0,t.chartSize),this.ctx.closePath(),this.ctx.restore(),this.drawShadow(),this.clearRect(-this.w,0,2*this.w,this.h),this.controller(t),this.drawText({textColor:this.pattern.color})},e.prototype.controller=function(t){this.pattern.turn+=10,1===this.pattern.nowState?(this.pattern.nowHeight--,this.pattern.shadow+=.2):2===this.pattern.nowState&&(this.pattern.nowHeight++,this.pattern.shadow-=.2),this.pattern.shadow=Math.floor(100*this.pattern.shadow)/100,this.pattern.nowHeight<=-t.chartSize&&this.pattern.nowHeight%8==0&&(t.delay+=.5,t.delay=Math.floor(100*t.delay)/100),this.pattern.nowHeight<=-t.maxHeight?this.pattern.nowState=2:this.pattern.nowHeight>=t.chartSize&&(this.pattern.nowState=1,t.delay=10,this.pattern.chart=this.randomState("charts"),this.pattern.color=this.randomState("chartColors"))},e.prototype.selectChart=function(t,e,i){switch(this.pattern.chart){case exports.PATTERN_CHART.RECT:this.drawRect(t,e,i);break;case exports.PATTERN_CHART.ARC:this.drawArc(t,e,i);break;case exports.PATTERN_CHART.TRIANGLE:this.drawTriangle(t,e,i);break;case exports.PATTERN_CHART.HEART:this.drawHeart(t,e,i);break;case exports.PATTERN_CHART.POLYGON:this.drawPolygon(t,e,i)}},e.prototype.randomState=function(t){var e=this.options;return e[t][parseInt(String(Math.random()*e[t].length))]},e.prototype.drawShadow=function(){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.globalAlpha=.2,this.ctx.strokeStyle=this.pattern.color,this.ctx.moveTo(-this.pattern.shadow/2,0),this.ctx.lineTo(this.pattern.shadow,0),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.restore()},e.prototype.drawRect=function(t,e,i){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-i/2,-i/2),this.ctx.fillRect(t,e,i,i),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawArc=function(t,e,i){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.arc(t,e,i/2,0,2*Math.PI),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawTriangle=function(t,e,i){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-i/2,-i/2*Math.sqrt(3)/2),this.ctx.moveTo(t,e),this.ctx.lineTo(i,0),this.ctx.lineTo(i/2,i/2*Math.sqrt(3)),this.ctx.lineTo(t,e),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawHeart=function(t,e,i){i/=2,this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(0,-2*i/2),this.ctx.moveTo(t,e),this.ctx.bezierCurveTo(i/2,-i,3*i,-i/2,e,2*i),this.ctx.moveTo(t,e),this.ctx.bezierCurveTo(-i/2,-i,3*-i,-i/2,e,2*i),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawPolygon=function(t,e,i){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-i/2,-i/2),this.ctx.moveTo(t,e),this.ctx.lineTo(i,e),this.ctx.lineTo(i+i/2,i/2),this.ctx.lineTo(i,i/2+i/2),this.ctx.lineTo(t,i),this.ctx.lineTo(t-i/2,i-i/2),this.ctx.lineTo(t,e),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.setShadow=function(){var t=this.options;this.ctx.shadowColor=this.pattern.color,this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur},e}(f),G={rollGap:12,childNum:4,rollSize:16,showChild:!0,chart:exports.ROLL_CHART.WHEEL,windmills:["#1ab3ea","#de6834","#30925d","#f48ea5"],windmillPointColor:"#f2c31f",fixed:!1},M=[{key:"childNum",message:"chartSize value 4-10",limit:function(t){return t>=4&&t<=10}},{key:"delay",message:"Roll.delay not allowed update",limit:function(t){return t===n().delay}}],H=function(t){function e(e,i,o,n,s){var a=t.call(this,e,i,o,n,s,G,M)||this;return a.Roll={turn:1,nowX:a.options.fixed?0:a.options.childNum/2*(a.options.rollSize+a.options.rollGap)+a.options.rollGap/2,state:2,child:[]},a.run(a.draw),a}return w(e,t),e.prototype.draw=function(){this.clearRect(),this.drawGround(),this.drawChild(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(-this.Roll.nowX,0),this.ctx.rotate(this.Roll.turn*Math.PI/180),this.selectChart(),this.controller(),this.ctx.restore();var t=this.options;this.drawText({esGap:t.rollSize})},e.prototype.selectChart=function(){switch(this.options.chart){case exports.ROLL_CHART.RECT:this.drawRect();break;case exports.ROLL_CHART.WHEEL:this.drawWheel();break;case exports.ROLL_CHART.WINDMILL:this.drawWindmill()}},e.prototype.controller=function(){var t=this.options;if(1===this.Roll.state&&(this.Roll.turn-=10,t.delay<20&&!t.fixed&&(t.delay+=2)),2===this.Roll.state&&(this.Roll.turn+=10,t.delay>10&&!t.fixed&&(t.delay-=5)),!t.fixed){this.Roll.nowX<=-t.childNum/2*(t.rollSize+t.rollGap/1.6)&&(this.Roll.state=1),this.Roll.nowX>=t.childNum/2*(t.rollSize+t.rollGap)+t.rollGap/2&&(this.Roll.state=2),1===this.Roll.state&&this.Roll.nowX++,2===this.Roll.state&&this.Roll.nowX--;var e=this.Roll.child;this.Roll.nowX%(t.rollSize+t.rollGap)==0&&2===this.Roll.state&&e.push({turn:this.Roll.turn,x:this.Roll.nowX}),1===this.Roll.state&&e[e.length-1]&&e[e.length-1].x===this.Roll.nowX&&this.Roll.child.pop()}},e.prototype.drawRect=function(){var t=this.options;this.ctx.save(),this.setShadow(),this.ctx.translate(-t.rollSize/2,-t.rollSize/2),this.ctx.fillRect(0,0,t.rollSize,t.rollSize),this.ctx.restore()},e.prototype.drawWheel=function(){var t=this.options;this.ctx.save(),this.ctx.lineWidth=4,this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize/6,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize/2,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath();for(var e=0;e<6;e++)this.ctx.beginPath(),this.ctx.moveTo(0,t.rollSize/2),this.ctx.lineTo(0,t.rollSize),this.ctx.stroke(),this.ctx.rotate(60*Math.PI/180),this.ctx.closePath();this.ctx.restore()},e.prototype.drawWindmill=function(){var t=this.options;this.ctx.save();for(var e=0;e0?e.some((function(e){return t.classList.contains(e)}))&&this.skeleton.push({title:t.nodeName,element:t}):this.skeleton.push({title:t.nodeName,element:t})},e.prototype.controller=function(t){for(var e=this.options,i=0,o=Array.from(t);i=.9&&(this.state=2),this.colorFlow<=.1&&(this.state=1),1===this.state&&(this.colorFlow+=.06),2===this.state&&(this.colorFlow-=.06))},e}(f)};function X(t,e,i,o,n){try{if(!n.$store)return;var s=n.$store.model;if(s)s.initContextCall(s.modelDefOptions,s.limits,s.modelDefCall);else{s=o.custom?new o.custom(t,e,i,o,n):new F[o.model](t,e,i,o,n)}}catch(t){l.error("draw error("+t+")")}}var B=window;function K(){var t;return(t={})[exports.HOOKS_CALL_KEY.BEFORE_CLOSE]=[],t[exports.HOOKS_CALL_KEY.CLOSED]=[],t}function $(t){var e;return(e={})[exports.HOOKS_CALL_KEY.BEFORE_CLOSE]=function(e){t[exports.HOOKS_CALL_KEY.BEFORE_CLOSE].push(e)},e[exports.HOOKS_CALL_KEY.CLOSED]=function(e){t[exports.HOOKS_CALL_KEY.CLOSED].push(e)},e}var j=window;function U(t,e,i,o){var n=e.pointerEvents?t.scrollWidth:t.clientWidth,s=e.pointerEvents?t.scrollHeight:t.clientHeight,a=j.getComputedStyle(t),r=t.style,h=o.style;return e.type!==exports.LOADING_TYPES.DOM||e.pointerEvents||(t.style.pointerEvents="none"),a.position&&"static"!==a.position||(r.position="relative"),o.id=i,h.opacity="0",h.position="absolute",h.left="".concat(e.pointerEvents?0:t.scrollLeft,"px"),h.top="".concat(e.pointerEvents?0:t.scrollTop,"px"),h.zIndex=e.zIndex,h.transition="".concat(e.delayInto/1e3,"s ease-in-out"),h.backgroundColor=e.bgColor,h.borderRadius=a.borderRadius,"htmlcanvaselement"===d(o)?Z(o,n,s):"htmldivelement"===d(o)&&(h.width="".concat(n,"px"),h.height="".concat(s,"px"),h.display="flex",h.alignItems="center",h.justifyContent="center"),t.append(o),j.setTimeout((function(){return h.opacity="1"}),0),x(t,(function(){t.$store&&(t.$store.loadingId=i)})),t}function Z(t,e,i){var o=j.devicePixelRatio||1;t.width=e*o,t.height=i*o,t.style.width="".concat(e,"px"),t.style.height="".concat(i,"px")}var q=window,V=function(){function t(t){this.canvas=null,this.htmlElement=null,this.loadingId=null,this.element=null,this.hooks=null,this.resizeTimeId=null,this.options=Object.assign(n(),t)}return t.prototype.resize=function(t,e){var i=this;this.resizeTimeId||(this.resizeTimeId=q.setTimeout((function(){var o=e,n=t.clientWidth,s=t.clientHeight;o.width>t.clientWidth&&(n=t.offsetWidth,s=t.offsetHeight),i.canvas?(Z(o,n,s),t.$store&&X(n,s,o,i.options,t)):i.htmlElement&&(i.htmlElement.style.width="".concat(n,"px"),i.htmlElement.style.height="".concat(s,"px")),i.resizeTimeId=null}),this.options.delayInto))},t.prototype.close=function(t,e){var i=this,o=this.options,n=t.$store;q.setTimeout((function(){!function(t,e,i){i.style.opacity="0",e.type!==exports.LOADING_TYPES.DOM&&(t.style.boxShadow="none")}(t,o,e),o.type!==exports.LOADING_TYPES.DOM||o.pointerEvents||(t.style.pointerEvents="auto"),x(t,(function(){n&&(n.model=null,i.callEvent(exports.HOOKS_CALL_KEY.BEFORE_CLOSE),n.animationId&&p(n.animationId)),o.type!==exports.LOADING_TYPES.DOM?t.remove():e.remove(),i.loadingId=null,i.callEvent(exports.HOOKS_CALL_KEY.CLOSED),i.hooks=K()}))}),n&&!n.loadingId?o.delayInto:0)},t.prototype.draw=function(t){var e=this.options;if(e.html){var i={content:B.document.createElement("div"),loadingId:u()},o=i.content,n=i.loadingId;this.htmlElement=o,this.htmlElement.innerHTML=e.html,this.loadingId=n,this.element=U(t,e,n,o)}else{var s={canvas:B.document.createElement("canvas"),hooks:K(),loadingId:u()},a=s.canvas,r=s.hooks;n=s.loadingId;this.canvas=a,this.hooks=r,this.loadingId=n,function(t,e,i){t.$store={options:e,animationId:void 0,loadingId:null,model:null,hookCall:$(i)}}(t,e,r),this.element=U(t,e,n,a),t.$store?X(a.offsetWidth,a.offsetHeight,a,this.options,t):l.error("WebLoading:canvas or ctx null")}},t.prototype.callEvent=function(t){this.hooks&&this.hooks[t].forEach((function(t){t()}))},t}(),J=window,Q=function(){function t(t){this.options=t,this.extendEl=this.initStyle()}return t.prototype.initStyle=function(){this.extendEl=J.document.createElement("div");var t=this.options,e="100vw",i="100vh",o="0px";return t&&(this.extendEl.classList.add("wl_"+(t.extendClass||"loading")),t.type===exports.LOADING_TYPES.MINI&&(e="180px",i="160px",o="10px"),this.extendEl.style.cssText="\n position:fixed;\n width:".concat(e,";\n height:").concat(i,";\n top:50%;\n left:50%;\n transform:translate(-50%, -50%);\n border-radius: ").concat(o,";\n z-index: ").concat(t.zIndex,";\n box-shadow:\n 2.8px 2.8px 2.2px rgba(0, 0, 0, 0.02),\n 6.7px 6.7px 5.3px rgba(0, 0, 0, 0.028),\n 12.5px 12.5px 10px rgba(0, 0, 0, 0.035),\n 22.3px 22.3px 17.9px rgba(0, 0, 0, 0.042),\n 41.8px 41.8px 33.4px rgba(0, 0, 0, 0.05),\n 100px 100px 80px rgba(0, 0, 0, 0.07)\n ;\n ")),J.document.body.appendChild(this.extendEl),this.extendEl},t.prototype.getElement=function(){return this.extendEl},t}(),tt=window;function et(t){var e=new V(t),i=null;function o(t){if(e.element){var i=e.canvas;e.htmlElement&&(i=e.htmlElement),i?e[t](e.element,i):l.warn("Animation element not found!")}}return{loading:function(t,o){var n=Object.assign(e.options,o);if(!e.loadingId&&!i)if(n.type!==exports.LOADING_TYPES.DOM&&(t=new Q(n).getElement()),t){var s=new Promise((function(t){tt.setTimeout((function(){t(!0)}),n.notFeel)})),a=new Promise((function(t){i=t}));Promise.race([s,a]).then((function(o){o?e.draw(t):n.type!==exports.LOADING_TYPES.DOM&&t.remove(),i=null}))}else l.error("The loading function cannot find an HTMLElement element!")},resize:function(){o("resize")},close:function(){i&&i(!1),o("close")},update:function(t){var i=e.canvas,o=Object.assign(e.options,t),n=e.element;i&&o&&n&&n.$store&&X(i.offsetWidth,i.offsetHeight,i,o,n)},getOptions:function(){return e.options},getLoadingId:function(){return e.loadingId}}}function it(t,e){return et(Object.assign(n(),e||{},{type:t}))}var ot=window;ot.BaseModel=f,ot.initLoading=function(t){return et(t)},ot.miniLoading=function(t){return it(exports.LOADING_TYPES.MINI,t)},ot.fullLoading=function(t){return it(exports.LOADING_TYPES.FULL,t)},exports.BaseModel=f,exports.fullLoading=function(t){return it(exports.LOADING_TYPES.FULL,t)},exports.initLoading=et,exports.miniLoading=function(t){return it(exports.LOADING_TYPES.MINI,t)}; 2 | -------------------------------------------------------------------------------- /dist/web-loading.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @description 包文档说明 4 | */ 5 | 6 | /** 7 | * @description 日志输出类 8 | * @public 9 | */ 10 | declare class $Log { 11 | /** 12 | * @description 输出信息日志 13 | * @param {string} message - 日志内容 14 | */ 15 | static info(message: string): void; 16 | /** 17 | * @description 输出警告日志 18 | * @param {string} message - 日志内容 19 | */ 20 | static warn(message: string): void; 21 | /** 22 | * @description 输出错误日志 23 | * @param {string} message - 日志内容 24 | */ 25 | static error(message: string): void; 26 | /** 27 | * @description 调用日志输出 28 | * @param {string} message - 日志内容 29 | * @param {LOG_TYPES} type - 日志类型 30 | * @param {LogConfigType} config - 日志配置 31 | */ 32 | static call(message: string, type?: LOG_TYPES, config?: LogConfigType): void; 33 | } 34 | 35 | /** 36 | * @description 基础模型类 37 | * @public 38 | */ 39 | export declare class BaseModel { 40 | w: number; 41 | h: number; 42 | canvas: HTMLCanvasElement; 43 | ctx: CanvasRenderingContext2D; 44 | options: Required; 45 | element: ElementType; 46 | modelDefOptions: T | undefined; 47 | limits: Array | undefined; 48 | modelDefCall: ((model: BaseModel) => void) | undefined; 49 | webLog: $Log; 50 | private stepClear; 51 | /** 52 | * @description 自定义基础模型 53 | * @param {number} w - Canvas宽度 54 | * @param {number} h - Canvas高度 55 | * @param {HTMLCanvasElement} canvas - Canvas元素 56 | * @param {Required} options - 配置选项 57 | * @param {ElementType} element - 容器元素 58 | * @param {T} [modelDefOptions] - 模型默认选项(可选) 59 | * @param {Array} [limits] - 模型默认限制(可选) 60 | * @param {(model: BaseModel) => void} [modelDefCall] - 提供模型初始化的回调函数,通常在模型中初始化"canvas"或"画笔"(可选) 61 | */ 62 | constructor(w: number, h: number, canvas: HTMLCanvasElement, options: Required, element: ElementType, modelDefOptions?: T, limits?: Array, modelDefCall?: (model: BaseModel) => void); 63 | private _$initBaseContext; 64 | private _$initEvent; 65 | /** 66 | * @description 封装requestAnimationFrame触发动画针 67 | * @param {() => void} fun - 触发函数 68 | * @private 69 | */ 70 | private _$animationFrame; 71 | /** 72 | * @description 初始化画笔属性 73 | * @param {T} [modelDefOptions] - 提供模型初始化的选项 74 | * @param {Array} [limits] - 提供模型初始化的限制 75 | * @param {(model: BaseModel) => void} [modelDefCall] - 提供模型初始化的回调函数 76 | */ 77 | initContextCall(modelDefOptions?: T, limits?: Array, modelDefCall?: (model: BaseModel) => void): void; 78 | /** 79 | * @description 开始动画 80 | * @param {() => void} fun - 动画函数 81 | */ 82 | run(fun: () => void): void; 83 | /** 84 | * @description 取消animationFrame动画针 85 | * @param {number} id - 动画ID 86 | */ 87 | clearAnimationFrame(id: number): void; 88 | /** 89 | * @description 清空画布 90 | * @param {number} [x] - x坐标 91 | * @param {number} [y] - y坐标 92 | * @param {number} [w_r] - 宽度或半径 93 | * @param {number} [h] - 高度 94 | */ 95 | clearRect(x?: number, y?: number, w_r?: number, h?: number): void; 96 | /** 97 | * @description 绘制圆角矩形 98 | * @param {number} x - x坐标 99 | * @param {number} y - y坐标 100 | * @param {number} w - 宽度 101 | * @param {number} h - 高度 102 | * @param {number} r - 圆角半径 103 | */ 104 | drawRadiusRect(x: number, y: number, w: number, h: number, r: number): void; 105 | /** 106 | * @description 绘制文本 107 | * @param {DrawTextParamsType} [params] - 文本参数 108 | * DrawTextParamsType: 109 | * esGap?: 额外空隙 110 | * x?: X轴位置 111 | * text?: 文本内容 112 | * textColor?: 文本颜色 113 | */ 114 | drawText(params?: DrawTextParamsType): void; 115 | } 116 | 117 | /** 118 | * @description 豆形模型配置类型 119 | * @public 120 | */ 121 | export declare interface BeanOptionsType extends OptionsType { 122 | beanSize?: number; 123 | pointLength?: number; 124 | } 125 | 126 | /** 127 | * @description 圆形动作枚举 128 | * @public 129 | */ 130 | export declare enum CIRCULAR_ACTION { 131 | COLLISION = "collision", 132 | ROTATE = "rotate" 133 | } 134 | 135 | /** 136 | * @description 圆形模型配置类型 137 | * @public 138 | */ 139 | export declare interface CircularOptionsType extends OptionsType { 140 | arcSize?: number; 141 | arcGap?: number; 142 | arcColors?: Array; 143 | action?: CIRCULAR_ACTION; 144 | } 145 | 146 | /** 147 | * @description 时钟模型配置类型 148 | * @public 149 | */ 150 | export declare interface ClockOptionsType extends OptionsType { 151 | textTime?: 'time' | 's' | ''; 152 | lineColors?: Array; 153 | lineCap?: CanvasLineCap; 154 | lineWidth?: number; 155 | clockSize?: number; 156 | clockGap?: number; 157 | hLine?: boolean; 158 | mLine?: boolean; 159 | sLine?: boolean; 160 | } 161 | 162 | /** 163 | * @description 绘制文本参数类型 164 | * @public 165 | */ 166 | export declare interface DrawTextParamsType { 167 | esGap?: number; 168 | x?: number; 169 | text?: string; 170 | textColor?: string; 171 | } 172 | 173 | /** 174 | * @description 元素存储类型接口 175 | * @public 176 | */ 177 | export declare interface ElementStoreType { 178 | options: OptionsType; 179 | animationId: number | undefined; 180 | loadingId: string | null; 181 | hookCall: HooksCallType; 182 | model: BaseModel | null; 183 | } 184 | 185 | /** 186 | * @description 元素类型接口 187 | * @public 188 | */ 189 | export declare interface ElementType extends HTMLElement { 190 | loadingId?: string | null; 191 | $store?: ElementStoreType; 192 | } 193 | 194 | /** 195 | * @description 全屏加载 196 | * @param {OptionsType} options - 配置选项 197 | * @returns {LoadingType} 返回加载操作对象 198 | * @public 199 | */ 200 | export declare function fullLoading(options?: OptionsType): LoadingType; 201 | 202 | /** 203 | * @description 齿轮模型配置类型 204 | * @public 205 | */ 206 | export declare interface GearOptionsType extends OptionsType { 207 | lineStart?: number; 208 | lineEnd?: number; 209 | lineStartSkew?: number; 210 | lineEndSkew?: number; 211 | lineWidth?: number; 212 | lineCap?: CanvasLineCap; 213 | lineNum?: number; 214 | direction?: boolean; 215 | } 216 | 217 | /** 218 | * @description 钩子调用键枚举 219 | * @public 220 | */ 221 | export declare enum HOOKS_CALL_KEY { 222 | BEFORE_CLOSE = "beforeClose", 223 | CLOSED = "closed" 224 | } 225 | 226 | /** 227 | * @description 钩子调用类型 228 | * 映射键是枚举 229 | */ 230 | declare type HooksCallType = { 231 | [key in T]: (...args: any[]) => void; 232 | }; 233 | 234 | /** 235 | * @description 图片模型配置类型 236 | * @public 237 | */ 238 | export declare interface ImageOptionsType extends OptionsType { 239 | src?: string; 240 | width?: number; 241 | height?: number; 242 | turn?: boolean; 243 | } 244 | 245 | /** 246 | * @description 初始化加载动画 247 | * @param {OptionsType} options - 配置选项 248 | * @returns {LoadingType} 返回加载操作对象 249 | * @public 250 | */ 251 | export declare function initLoading(options?: OptionsType): LoadingType; 252 | 253 | /** 254 | * @description 限制类型接口 255 | * @public 256 | */ 257 | export declare interface LimitType { 258 | key: string; 259 | message: string; 260 | limit: (key: any) => boolean; 261 | } 262 | 263 | /** 264 | * @description 支持的加载方法 265 | * @public 266 | */ 267 | export declare enum LOADING_TYPES { 268 | DOM = "dom", 269 | FULL = "full", 270 | MINI = "mini" 271 | } 272 | 273 | /** 274 | * @description 加载类型接口 275 | * @public 276 | */ 277 | export declare interface LoadingType { 278 | loading: (dom: ElementType, options?: OptionsType) => void; 279 | resize: () => void; 280 | close: () => void; 281 | update: (options?: OptionsType) => void; 282 | getOptions: () => OptionsType; 283 | getLoadingId: () => string | null; 284 | } 285 | 286 | /** 287 | * @description 日志类型枚举 288 | * @public 289 | */ 290 | export declare enum LOG_TYPES { 291 | INFO = 1, 292 | WARN = 2, 293 | ERROR = 3 294 | } 295 | 296 | /** 297 | * @description 日志配置类型 298 | * @public 299 | */ 300 | export declare type LogConfigType = { 301 | color?: string; 302 | bgColor?: string; 303 | }; 304 | 305 | /** 306 | * @description 迷你加载 307 | * @param {OptionsType} options - 配置选项 308 | * @returns {LoadingType} 返回加载操作对象 309 | * @public 310 | */ 311 | export declare function miniLoading(options?: OptionsType): LoadingType; 312 | 313 | /** 314 | * @description 支持的模型类型 315 | * @public 316 | */ 317 | export declare enum MODEL_TYPES { 318 | GEAR = "Gear", 319 | RING = "Ring", 320 | ZOOM = "Zoom", 321 | PATTERN = "Pattern", 322 | CLOCK = "Clock", 323 | BEAN = "Bean", 324 | ROLL = "Roll", 325 | CIRCULAR = "Circular", 326 | IMG = "Img", 327 | SKELETON = "Skeleton" 328 | } 329 | 330 | /** 331 | * @description 配置选项接口 332 | * @public 333 | */ 334 | export declare interface OptionsType { 335 | custom?: typeof BaseModel | null; 336 | type?: LOADING_TYPES; 337 | extendClass?: string | null | undefined; 338 | model?: MODEL_TYPES; 339 | html?: string; 340 | text?: string; 341 | textGap?: number; 342 | fontSize?: number; 343 | fontFamily?: string; 344 | delay?: number; 345 | delayInto?: number; 346 | notFeel?: number; 347 | optimization?: boolean; 348 | zIndex?: string; 349 | themeColor?: string; 350 | bgColor?: string; 351 | shadowColor?: string; 352 | shadowOffsetX?: number; 353 | shadowOffsetY?: number; 354 | shadowBlur?: number; 355 | pointerEvents?: boolean; 356 | toast?: boolean; 357 | } 358 | 359 | /** 360 | * @description 图案类型枚举 361 | * @public 362 | */ 363 | export declare enum PATTERN_CHART { 364 | RECT = "rect", 365 | ARC = "arc", 366 | TRIANGLE = "triangle", 367 | HEART = "heart", 368 | POLYGON = "polygon" 369 | } 370 | 371 | /** 372 | * @description 图案模型配置类型 373 | * @public 374 | */ 375 | export declare interface PatternOptionsType extends OptionsType { 376 | charts?: Array; 377 | chartSize?: number; 378 | chartColors?: Array; 379 | maxHeight?: number; 380 | } 381 | 382 | /** 383 | * @description 环形模型配置类型 384 | * @public 385 | */ 386 | export declare interface RingOptionsType extends OptionsType { 387 | ringGap?: number; 388 | arcGap?: number; 389 | lineWidth?: number; 390 | ringNum?: number; 391 | radius?: number; 392 | lineCap?: CanvasLineCap; 393 | turn?: number; 394 | ringsTurn?: Array; 395 | direction?: boolean; 396 | } 397 | 398 | /** 399 | * @description 滚动图表类型枚举 400 | * @public 401 | */ 402 | export declare enum ROLL_CHART { 403 | RECT = "rect", 404 | WHEEL = "wheel", 405 | WINDMILL = "windmill" 406 | } 407 | 408 | /** 409 | * @description 滚动模型配置类型 410 | * @public 411 | */ 412 | export declare interface RollOptionsType extends OptionsType { 413 | rollGap?: number; 414 | rollSize?: number; 415 | showChild?: boolean; 416 | childNum?: number; 417 | chart?: ROLL_CHART; 418 | windmills?: Array; 419 | windmillPointColor?: string; 420 | fixed?: boolean; 421 | } 422 | 423 | /** 424 | * @description 骨架屏模型配置类型 425 | * @public 426 | */ 427 | export declare interface SkeletonOptionsType extends OptionsType { 428 | skeletonColor?: string; 429 | skeletonAnimationColor?: string; 430 | radius?: number; 431 | animation?: boolean; 432 | deep?: boolean; 433 | appointElementClass?: Array; 434 | } 435 | 436 | /** 437 | * @description 缩放动作枚举 438 | * @public 439 | */ 440 | export declare enum ZOOM_ACTION { 441 | SCALE = "scale", 442 | WAVE = "wave", 443 | HEIGHT = "height" 444 | } 445 | 446 | /** 447 | * @description 缩放模型配置类型 448 | * @public 449 | */ 450 | export declare interface ZoomOptionsType extends OptionsType { 451 | maxSize?: number; 452 | zoomGap?: number; 453 | zoomHeight?: number; 454 | zoomNum?: number; 455 | zoomColors?: Array; 456 | lineCap?: CanvasLineCap; 457 | lineWidth?: number; 458 | action: ZOOM_ACTION; 459 | direction?: boolean; 460 | } 461 | 462 | export { } 463 | -------------------------------------------------------------------------------- /dist/web-loading.min.js: -------------------------------------------------------------------------------- 1 | !function(t,i){"object"==typeof exports&&"undefined"!=typeof module?i(exports):"function"==typeof define&&define.amd?define(["exports"],i):i((t="undefined"!=typeof globalThis?globalThis:t||self).rustOption={})}(this,(function(t){"use strict";var i,e,o,n;function s(){return{custom:null,html:"",type:t.LOADING_TYPES.DOM,extendClass:"extend",model:t.MODEL_TYPES.GEAR,text:"加载中...",textGap:8,fontSize:12,fontFamily:"Microsoft YaHei",delay:65,notFeel:0,delayInto:320,optimization:!1,zIndex:"2001",themeColor:"rgba(64,158,255,1)",bgColor:"rgba(0, 0, 0, 0.8)",shadowColor:"rgba(64,158,255,0.6)",shadowOffsetX:2,shadowOffsetY:2,shadowBlur:5,pointerEvents:!1,toast:!0}}t.LOADING_TYPES=void 0,(i=t.LOADING_TYPES||(t.LOADING_TYPES={})).DOM="dom",i.FULL="full",i.MINI="mini",t.MODEL_TYPES=void 0,(e=t.MODEL_TYPES||(t.MODEL_TYPES={})).GEAR="Gear",e.RING="Ring",e.ZOOM="Zoom",e.PATTERN="Pattern",e.CLOCK="Clock",e.BEAN="Bean",e.ROLL="Roll",e.CIRCULAR="Circular",e.IMG="Img",e.SKELETON="Skeleton",t.HOOKS_CALL_KEY=void 0,(o=t.HOOKS_CALL_KEY||(t.HOOKS_CALL_KEY={})).BEFORE_CLOSE="beforeClose",o.CLOSED="closed",t.LOG_TYPES=void 0,(n=t.LOG_TYPES||(t.LOG_TYPES={}))[n.INFO=1]="INFO",n[n.WARN=2]="WARN",n[n.ERROR=3]="ERROR";var a,r,h,l,c=function(){function i(){}return i.info=function(i){this.call(i,t.LOG_TYPES.INFO)},i.warn=function(i){this.call(i,t.LOG_TYPES.WARN)},i.error=function(i){this.call(i,t.LOG_TYPES.ERROR)},i.call=function(i,e,o){void 0===e&&(e=t.LOG_TYPES.INFO),void 0===o&&(o={color:s().themeColor,bgColor:s().bgColor});var n=o.bgColor;2===e&&(n="#fffbe5"),3===e&&(n="#fff0f0");var a="\n background:".concat(n,";\n font-size:14px;\n color:").concat(o.color,";\n padding: 4px;\n border: 1px solid;");console.log("%c web-loading:".concat(i," "),a)},i}();function d(t){switch(u(t)){case"object":return Object.keys(t).length>0;case"array":return t.length>0;case"undefined":default:return void 0!==t;case"null":return null!==t}}function p(t){window.requestAnimationFrame?window.cancelAnimationFrame(t):window.clearInterval(t)}function u(t){try{return Object.prototype.toString.call(t).split(" ")[1].split("]")[0].toLowerCase()}catch(t){return"not-type"}}function x(t,i){var e=null,o={transition:"transitionend",OTransition:"oTransitionEnd",MozTransition:"transitionend",WebkitTransition:"webkitTransitionEnd"};for(var n in o)if(void 0!==t.style[n]){e=o[n];break}if(e){var s=function(){i(),t.removeEventListener(e,s)};t.addEventListener(e,s)}else i()}function f(){var t=String(Date.now());return window.crypto&&window.crypto.randomUUID&&(t=window.crypto.randomUUID()),"wl_"+t.replace(/-/g,"")}t.ZOOM_ACTION=void 0,(a=t.ZOOM_ACTION||(t.ZOOM_ACTION={})).SCALE="scale",a.WAVE="wave",a.HEIGHT="height",t.PATTERN_CHART=void 0,(r=t.PATTERN_CHART||(t.PATTERN_CHART={})).RECT="rect",r.ARC="arc",r.TRIANGLE="triangle",r.HEART="heart",r.POLYGON="polygon",t.ROLL_CHART=void 0,(h=t.ROLL_CHART||(t.ROLL_CHART={})).RECT="rect",h.WHEEL="wheel",h.WINDMILL="windmill",t.CIRCULAR_ACTION=void 0,(l=t.CIRCULAR_ACTION||(t.CIRCULAR_ACTION={})).COLLISION="collision",l.ROTATE="rotate";var m=function(){function t(t,i,e,o,n,s,a,r){this.modelDefOptions=void 0,this.limits=void 0,this.modelDefCall=void 0,this.webLog=c,this.stepClear=1,this.w=t,this.h=i,this.canvas=e,this.ctx=e.getContext("2d"),this.options=o,this.element=n,this.initContextCall(s,a,r)}return t.prototype._$initBaseContext=function(){this.clearRect(),this.ctx.resetTransform();var t=this.options,i=this.canvas.width,e=this.canvas.height;this.ctx.fillStyle=t.themeColor,this.ctx.strokeStyle=t.themeColor,this.ctx.shadowColor=t.shadowColor,this.ctx.font="".concat(t.fontSize,"px ").concat(t.fontFamily," small-caps"),this.ctx.textAlign="center",this.ctx.textBaseline="middle",this.ctx.translate(i/2,e/2);var o=window.devicePixelRatio||1;this.ctx.scale(o,o),this.ctx.save()},t.prototype._$initEvent=function(){var t=this;this.element.$store&&this.element.$store.hookCall.beforeClose((function(){t.clearRect()}))},t.prototype._$animationFrame=function(t){var i=this,e=this.element.$store;if(e){window.requestAnimationFrame||(e.animationId=window.setInterval(t,this.options.delay));var o=Date.now()+this.options.delay;t.call(this);var n=function(){Date.now()>o&&(t.call(i),o=Date.now()+i.options.delay),e.animationId=window.requestAnimationFrame(n)};e.animationId=window.requestAnimationFrame(n)}},t.prototype.initContextCall=function(t,i,e){var o=this;this._$initBaseContext(),this._$initEvent(),d(t)&&(this.modelDefOptions=t,this.options=Object.assign(t,this.options),this.element.$store&&(this.element.$store.options=this.options),i&&i.length&&this.options.toast&&i.forEach((function(t){var i=o.options[t.key];d(i)&&!t.limit(i)&&c.warn(t.message)}))),d(i)&&(this.limits=i),d(e)&&e.call(this,this)},t.prototype.run=function(t){var i=this.element.$store;i&&i.animationId&&this.clearAnimationFrame(i.animationId),this._$animationFrame(t)},t.prototype.clearAnimationFrame=function(t){p(t)},t.prototype.clearRect=function(t,i,e,o){var n=this.canvas.width,s=this.canvas.height;if(d(t)&&d(i)&&d(e)&&d(o))this.ctx.clearRect(t,i,e,o);else if(d(t)&&d(i)&&d(e)&&!d(o)){var a=e-this.stepClear,r=Math.sqrt(e*e-a*a),h=t-a,l=i-r,c=2*a,p=2*r;this.stepClear<=e?(this.ctx.clearRect(h,l,c,p),this.stepClear+=1,this.clearRect(t,i,e)):this.stepClear=1}else this.ctx.clearRect(-n,-s,2*n,2*s)},t.prototype.drawRadiusRect=function(t,i,e,o,n){this.ctx.beginPath(),this.ctx.arc(t+n,i+n,n,1*Math.PI,1.5*Math.PI),this.ctx.lineTo(t+e-n,i),this.ctx.arc(t+e-n,i+n,n,1.5*Math.PI,0),this.ctx.lineTo(t+e,i+o-n),this.ctx.arc(t+e-n,i+o-n,n,0,.5*Math.PI),this.ctx.lineTo(t+n,i+o),this.ctx.arc(t+n,i+o-n,n,.5*Math.PI,Math.PI),this.ctx.lineTo(t,i+n),this.ctx.closePath()},t.prototype.drawText=function(t){var i=this.options,e=Object.assign({esGap:i.textGap||0,x:0,text:i.text||"",textColor:i.themeColor||"rgba(64,158,255,1)"},t);this.ctx.save(),this.ctx.beginPath(),this.ctx.fillStyle=e.textColor,this.ctx.fillText(e.text,e.x,(i.textGap||0)+(i.fontSize||0)+e.esGap),this.ctx.closePath(),this.ctx.restore()},t}(),w=function(t,i){return w=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,i){t.__proto__=i}||function(t,i){for(var e in i)Object.prototype.hasOwnProperty.call(i,e)&&(t[e]=i[e])},w(t,i)};function v(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function e(){this.constructor=t}w(t,i),t.prototype=null===i?Object.create(i):(e.prototype=i.prototype,new e)}var g={lineStart:10,lineEnd:16,lineStartSkew:0,lineEndSkew:0,lineWidth:4,lineCap:"round",lineNum:10,direction:!0},S=[{key:"lineNum",message:"lineNum value 4-18",limit:function(t){return t>=4&&t<=18}}],C=function(t){function i(i,e,o,n,s){var a=t.call(this,i,e,o,n,s,g,S,(function(t){var i=t.options;t.ctx.lineCap=i.lineCap,t.ctx.lineWidth=i.lineWidth,t.ctx.save()}))||this;return a.optimization(a.options.textGap+a.options.lineEnd),a.aps=Array.from({length:a.options.lineNum},(function(t,i){return i})),a.run(a.draw),a}return v(i,t),i.prototype.draw=function(){this.clearRect(),this.controller(),this.drawGear();var t=this.options;this.drawText({esGap:t.lineEnd})},i.prototype.controller=function(){var t=this;this.options.direction?this.aps=this.aps.map((function(i){return i-1<=0?t.aps.length-1:i-1})):this.aps=this.aps.map((function(i){return i+1>t.aps.length?0:i+1}))},i.prototype.drawGear=function(){var t=this.options;this.ctx.save(),this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur;for(var i=0;ithis.h&&(this.options.lineStart=this.h/6-5,this.options.lineEnd=this.h/6,this.options.textGap=2)},i}(m),T={zoomGap:10,maxSize:16,zoomNum:5,lineWidth:10,zoomHeight:2,lineCap:"round",action:t.ZOOM_ACTION.SCALE,direction:!0,zoomColors:[]},y=[{key:"lineWidth",message:"lineWidth(default:10) <= maxSize(default:16)",limit:function(t){return t<=T.maxSize}},{key:"maxSize",message:"lineWidth(default:10) <= maxSize(default:16)",limit:function(t){return T.lineWidth<=t}}],O=function(i){function e(t,e,o,n,s){var a=i.call(this,t,e,o,n,s,T,y,(function(t){var i=t.options;t.ctx.lineCap=i.lineCap,t.ctx.lineWidth=i.lineWidth,t.ctx.translate(-(i.lineWidth*(i.zoomNum+1)+i.zoomGap*(i.zoomNum+1))/2,-i.zoomHeight/2),t.ctx.save()}))||this;return a.zoomIndex=a.options.direction?0:a.options.zoomNum-1,a.list=Array.from({length:a.options.zoomNum},(function(t,i){return Object.assign({value:a.options.lineWidth,state:0})})),a.run(a.draw),a}return v(e,i),e.prototype.draw=function(){this.clearRect(),this.drawZoom();var t=this.options;this.drawText({esGap:t.maxSize,x:(t.lineWidth*(t.zoomNum+1)+t.zoomGap*(t.zoomNum+1))/2}),this.controller()},e.prototype.controller=function(){var t=this.options;t.direction&&this.zoomIndex>=t.zoomNum?this.zoomIndex=0:t.direction&&this.zoomIndex<0&&(this.zoomIndex=t.zoomNum-1)},e.prototype.drawZoom=function(){for(var i=this.options,e=0;e=i.lineWidth&&this.list[e].value--,i.action===t.ZOOM_ACTION.SCALE&&(this.ctx.lineWidth=this.list[e].value),e===this.zoomIndex&&(this.list[e].value>i.maxSize&&(this.list[e].state=2,i.direction?this.zoomIndex++:this.zoomIndex-1>=0?this.zoomIndex--:this.zoomIndex=i.zoomNum-1),this.list[e].value<=i.lineWidth&&(this.list[e].state=1)),this.ctx.beginPath(),i.zoomColors.length>0&&i.zoomColors[e]?this.ctx.strokeStyle=i.zoomColors[e]:this.ctx.strokeStyle=i.themeColor;var o=0,n=i.zoomHeight;i.action!==t.ZOOM_ACTION.HEIGHT&&i.action!==t.ZOOM_ACTION.WAVE||(o=-this.list[e].value),i.action===t.ZOOM_ACTION.WAVE&&(n=-this.list[e].value),this.ctx.moveTo((e+1)*(i.lineWidth+i.zoomGap),o),this.ctx.lineTo((e+1)*(i.lineWidth+i.zoomGap),n),this.ctx.stroke(),this.ctx.closePath()}},e}(m),E={arcGap:Math.PI/4,ringGap:10,lineWidth:2,ringNum:2,radius:6,lineCap:"round",turn:10,ringsTurn:[Math.PI,Math.PI/4],direction:!0},I=[{key:"ringNum",message:"ringNum value 1-10",limit:function(t){return t>=1&&t<=10}},{key:"ringsTurn",message:"ringsTurn size ".concat(E.ringNum),limit:function(t){return t.length<=E.ringNum}}],b=function(t){function i(i,e,o,n,s){var a=t.call(this,i,e,o,n,s,E,I,(function(t){var i=t.options;t.ctx.lineCap=i.lineCap,t.ctx.lineWidth=i.lineWidth,t.ctx.save()}))||this;return a.rotate=10,a.run(a.draw),a}return v(i,t),i.prototype.draw=function(){this.clearRect(),this.controller();var t=this.options;this.drawText({esGap:t.ringNum*(t.radius+t.ringGap/2)})},i.prototype.controller=function(){this.ctx.save();var t=this.options,i=this.rotate*Math.PI/180*(t.direction?1:-1);this.ctx.rotate(i),this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur;for(var e=1;e<=t.ringNum;e++)this.drawRing(t.radius+(e-1)*t.ringGap,t.arcGap,t.ringsTurn&&t.ringsTurn.length>0?t.ringsTurn[e-1]:Math.PI/e);this.rotate+=t.turn,this.ctx.restore()},i.prototype.drawRing=function(t,i,e){void 0===i&&(i=1),void 0===e&&(e=0),this.ctx.beginPath(),this.ctx.arc(0,0,t,i+e,Math.PI+e),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t,Math.PI+i+e,e),this.ctx.stroke(),this.ctx.closePath()},i}(m),P={beanSize:15,pointLength:15},R=[{key:"pointLength",message:"pointLength value >= 5",limit:function(t){return t>=5}},{key:"beanSize",message:"beanSize value >= 5",limit:function(t){return t>=5}}],L=function(t){function i(i,e,o,n,s){var a=t.call(this,i,e,o,n,s,P,R)||this;return a.bean={turn:30,state:1,beanState:1,nowX:-a.options.pointLength*a.options.beanSize/2-3*a.options.beanSize,beans:Array.from({length:a.options.pointLength},(function(){return 1})),beanAnimaIndex:0},a.options.delay=10,a.run(a.draw),a}return v(i,t),i.prototype.draw=function(){var t=this.options;this.clearRect(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(this.bean.nowX,0),this.ctx.arc(0,0,t.beanSize,(360-this.bean.turn)*Math.PI/180,this.bean.turn*Math.PI/180,!0),this.ctx.lineTo(0,0),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore(),this.drawPoint(),this.drawFilter(),this.drawText({esGap:t.beanSize}),this.controller()},i.prototype.controller=function(){var t=this.options;this.bean.nowX>=t.pointLength*t.beanSize/2+2*t.beanSize&&(this.bean.nowX=-t.pointLength*t.beanSize/2-3*t.beanSize,this.bean.beanAnimaIndex=0),this.bean.nowX<=-t.pointLength*t.beanSize/2&&(this.bean.beanState=2),this.bean.turn<=0&&(this.bean.state=2),this.bean.turn>=30&&(this.bean.state=1),1===this.bean.state&&(this.bean.turn-=1),2===this.bean.state&&(this.bean.turn+=1),1===this.bean.beanState&&(this.bean.nowX-=1),2===this.bean.beanState&&(this.bean.nowX+=1)},i.prototype.drawPoint=function(){var t=this.options;this.ctx.save(),this.setShadow(),this.ctx.translate(-t.pointLength*t.beanSize/2,0);for(var i=0;i=5&&t<=24}},{key:"delay",message:"Pattern.delay not allowed update",limit:function(t){return t===s().delay}}],G=function(i){function e(t,e,o,n,s){var a=i.call(this,t,e,o,n,s,_,N,(function(t){t.options.delay=10}))||this;return a.pattern={color:a.randomState("chartColors"),nowHeight:10,chart:a.randomState("charts"),shadow:0,nowState:1,turn:0},a.run(a.draw),a}return v(e,i),e.prototype.draw=function(){var t=this.options;this.clearRect(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(0,this.pattern.nowHeight),this.ctx.rotate(this.pattern.turn/Math.PI*2),this.ctx.fillStyle=this.pattern.color,this.selectChart(0,0,t.chartSize),this.ctx.closePath(),this.ctx.restore(),this.drawShadow(),this.clearRect(-this.w,0,2*this.w,this.h),this.controller(t),this.drawText({textColor:this.pattern.color})},e.prototype.controller=function(t){this.pattern.turn+=10,1===this.pattern.nowState?(this.pattern.nowHeight--,this.pattern.shadow+=.2):2===this.pattern.nowState&&(this.pattern.nowHeight++,this.pattern.shadow-=.2),this.pattern.shadow=Math.floor(100*this.pattern.shadow)/100,this.pattern.nowHeight<=-t.chartSize&&this.pattern.nowHeight%8==0&&(t.delay+=.5,t.delay=Math.floor(100*t.delay)/100),this.pattern.nowHeight<=-t.maxHeight?this.pattern.nowState=2:this.pattern.nowHeight>=t.chartSize&&(this.pattern.nowState=1,t.delay=10,this.pattern.chart=this.randomState("charts"),this.pattern.color=this.randomState("chartColors"))},e.prototype.selectChart=function(i,e,o){switch(this.pattern.chart){case t.PATTERN_CHART.RECT:this.drawRect(i,e,o);break;case t.PATTERN_CHART.ARC:this.drawArc(i,e,o);break;case t.PATTERN_CHART.TRIANGLE:this.drawTriangle(i,e,o);break;case t.PATTERN_CHART.HEART:this.drawHeart(i,e,o);break;case t.PATTERN_CHART.POLYGON:this.drawPolygon(i,e,o)}},e.prototype.randomState=function(t){var i=this.options;return i[t][parseInt(String(Math.random()*i[t].length))]},e.prototype.drawShadow=function(){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.globalAlpha=.2,this.ctx.strokeStyle=this.pattern.color,this.ctx.moveTo(-this.pattern.shadow/2,0),this.ctx.lineTo(this.pattern.shadow,0),this.ctx.stroke(),this.ctx.beginPath(),this.ctx.restore()},e.prototype.drawRect=function(t,i,e){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-e/2,-e/2),this.ctx.fillRect(t,i,e,e),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawArc=function(t,i,e){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.arc(t,i,e/2,0,2*Math.PI),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawTriangle=function(t,i,e){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-e/2,-e/2*Math.sqrt(3)/2),this.ctx.moveTo(t,i),this.ctx.lineTo(e,0),this.ctx.lineTo(e/2,e/2*Math.sqrt(3)),this.ctx.lineTo(t,i),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawHeart=function(t,i,e){e/=2,this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(0,-2*e/2),this.ctx.moveTo(t,i),this.ctx.bezierCurveTo(e/2,-e,3*e,-e/2,i,2*e),this.ctx.moveTo(t,i),this.ctx.bezierCurveTo(-e/2,-e,3*-e,-e/2,i,2*e),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.drawPolygon=function(t,i,e){this.ctx.save(),this.ctx.beginPath(),this.setShadow(),this.ctx.translate(-e/2,-e/2),this.ctx.moveTo(t,i),this.ctx.lineTo(e,i),this.ctx.lineTo(e+e/2,e/2),this.ctx.lineTo(e,e/2+e/2),this.ctx.lineTo(t,e),this.ctx.lineTo(t-e/2,e-e/2),this.ctx.lineTo(t,i),this.ctx.fill(),this.ctx.closePath(),this.ctx.restore()},e.prototype.setShadow=function(){var t=this.options;this.ctx.shadowColor=this.pattern.color,this.ctx.shadowOffsetX=t.shadowOffsetX,this.ctx.shadowOffsetY=t.shadowOffsetY,this.ctx.shadowBlur=t.shadowBlur},e}(m),M={rollGap:12,childNum:4,rollSize:16,showChild:!0,chart:t.ROLL_CHART.WHEEL,windmills:["#1ab3ea","#de6834","#30925d","#f48ea5"],windmillPointColor:"#f2c31f",fixed:!1},H=[{key:"childNum",message:"chartSize value 4-10",limit:function(t){return t>=4&&t<=10}},{key:"delay",message:"Roll.delay not allowed update",limit:function(t){return t===s().delay}}],W=function(i){function e(t,e,o,n,s){var a=i.call(this,t,e,o,n,s,M,H)||this;return a.Roll={turn:1,nowX:a.options.fixed?0:a.options.childNum/2*(a.options.rollSize+a.options.rollGap)+a.options.rollGap/2,state:2,child:[]},a.run(a.draw),a}return v(e,i),e.prototype.draw=function(){this.clearRect(),this.drawGround(),this.drawChild(),this.ctx.save(),this.ctx.beginPath(),this.ctx.translate(-this.Roll.nowX,0),this.ctx.rotate(this.Roll.turn*Math.PI/180),this.selectChart(),this.controller(),this.ctx.restore();var t=this.options;this.drawText({esGap:t.rollSize})},e.prototype.selectChart=function(){switch(this.options.chart){case t.ROLL_CHART.RECT:this.drawRect();break;case t.ROLL_CHART.WHEEL:this.drawWheel();break;case t.ROLL_CHART.WINDMILL:this.drawWindmill()}},e.prototype.controller=function(){var t=this.options;if(1===this.Roll.state&&(this.Roll.turn-=10,t.delay<20&&!t.fixed&&(t.delay+=2)),2===this.Roll.state&&(this.Roll.turn+=10,t.delay>10&&!t.fixed&&(t.delay-=5)),!t.fixed){this.Roll.nowX<=-t.childNum/2*(t.rollSize+t.rollGap/1.6)&&(this.Roll.state=1),this.Roll.nowX>=t.childNum/2*(t.rollSize+t.rollGap)+t.rollGap/2&&(this.Roll.state=2),1===this.Roll.state&&this.Roll.nowX++,2===this.Roll.state&&this.Roll.nowX--;var i=this.Roll.child;this.Roll.nowX%(t.rollSize+t.rollGap)==0&&2===this.Roll.state&&i.push({turn:this.Roll.turn,x:this.Roll.nowX}),1===this.Roll.state&&i[i.length-1]&&i[i.length-1].x===this.Roll.nowX&&this.Roll.child.pop()}},e.prototype.drawRect=function(){var t=this.options;this.ctx.save(),this.setShadow(),this.ctx.translate(-t.rollSize/2,-t.rollSize/2),this.ctx.fillRect(0,0,t.rollSize,t.rollSize),this.ctx.restore()},e.prototype.drawWheel=function(){var t=this.options;this.ctx.save(),this.ctx.lineWidth=4,this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize/6,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize/2,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath(),this.ctx.beginPath(),this.ctx.arc(0,0,t.rollSize,0,2*Math.PI),this.ctx.stroke(),this.ctx.closePath();for(var i=0;i<6;i++)this.ctx.beginPath(),this.ctx.moveTo(0,t.rollSize/2),this.ctx.lineTo(0,t.rollSize),this.ctx.stroke(),this.ctx.rotate(60*Math.PI/180),this.ctx.closePath();this.ctx.restore()},e.prototype.drawWindmill=function(){var t=this.options;this.ctx.save();for(var i=0;i0?i.some((function(i){return t.classList.contains(i)}))&&this.skeleton.push({title:t.nodeName,element:t}):this.skeleton.push({title:t.nodeName,element:t})},i.prototype.controller=function(t){for(var i=this.options,e=0,o=Array.from(t);e=.9&&(this.state=2),this.colorFlow<=.1&&(this.state=1),1===this.state&&(this.colorFlow+=.06),2===this.state&&(this.colorFlow-=.06))},i}(m)};function B(t,i,e,o,n){try{if(!n.$store)return;var s=n.$store.model;if(s)s.initContextCall(s.modelDefOptions,s.limits,s.modelDefCall);else{s=o.custom?new o.custom(t,i,e,o,n):new X[o.model](t,i,e,o,n)}}catch(t){c.error("draw error("+t+")")}}var K=window;function $(){var i;return(i={})[t.HOOKS_CALL_KEY.BEFORE_CLOSE]=[],i[t.HOOKS_CALL_KEY.CLOSED]=[],i}function j(i){var e;return(e={})[t.HOOKS_CALL_KEY.BEFORE_CLOSE]=function(e){i[t.HOOKS_CALL_KEY.BEFORE_CLOSE].push(e)},e[t.HOOKS_CALL_KEY.CLOSED]=function(e){i[t.HOOKS_CALL_KEY.CLOSED].push(e)},e}var U=window;function Z(i,e,o,n){var s=e.pointerEvents?i.scrollWidth:i.clientWidth,a=e.pointerEvents?i.scrollHeight:i.clientHeight,r=U.getComputedStyle(i),h=i.style,l=n.style;return e.type!==t.LOADING_TYPES.DOM||e.pointerEvents||(i.style.pointerEvents="none"),r.position&&"static"!==r.position||(h.position="relative"),n.id=o,l.opacity="0",l.position="absolute",l.left="".concat(e.pointerEvents?0:i.scrollLeft,"px"),l.top="".concat(e.pointerEvents?0:i.scrollTop,"px"),l.zIndex=e.zIndex,l.transition="".concat(e.delayInto/1e3,"s ease-in-out"),l.backgroundColor=e.bgColor,l.borderRadius=r.borderRadius,"htmlcanvaselement"===u(n)?q(n,s,a):"htmldivelement"===u(n)&&(l.width="".concat(s,"px"),l.height="".concat(a,"px"),l.display="flex",l.alignItems="center",l.justifyContent="center"),i.append(n),U.setTimeout((function(){return l.opacity="1"}),0),x(i,(function(){i.$store&&(i.$store.loadingId=o)})),i}function q(t,i,e){var o=U.devicePixelRatio||1;t.width=i*o,t.height=e*o,t.style.width="".concat(i,"px"),t.style.height="".concat(e,"px")}var V=window,J=function(){function i(t){this.canvas=null,this.htmlElement=null,this.loadingId=null,this.element=null,this.hooks=null,this.resizeTimeId=null,this.options=Object.assign(s(),t)}return i.prototype.resize=function(t,i){var e=this;this.resizeTimeId||(this.resizeTimeId=V.setTimeout((function(){var o=i,n=t.clientWidth,s=t.clientHeight;o.width>t.clientWidth&&(n=t.offsetWidth,s=t.offsetHeight),e.canvas?(q(o,n,s),t.$store&&B(n,s,o,e.options,t)):e.htmlElement&&(e.htmlElement.style.width="".concat(n,"px"),e.htmlElement.style.height="".concat(s,"px")),e.resizeTimeId=null}),this.options.delayInto))},i.prototype.close=function(i,e){var o=this,n=this.options,s=i.$store;V.setTimeout((function(){!function(i,e,o){o.style.opacity="0",e.type!==t.LOADING_TYPES.DOM&&(i.style.boxShadow="none")}(i,n,e),n.type!==t.LOADING_TYPES.DOM||n.pointerEvents||(i.style.pointerEvents="auto"),x(i,(function(){s&&(s.model=null,o.callEvent(t.HOOKS_CALL_KEY.BEFORE_CLOSE),s.animationId&&p(s.animationId)),n.type!==t.LOADING_TYPES.DOM?i.remove():e.remove(),o.loadingId=null,o.callEvent(t.HOOKS_CALL_KEY.CLOSED),o.hooks=$()}))}),s&&!s.loadingId?n.delayInto:0)},i.prototype.draw=function(t){var i=this.options;if(i.html){var e={content:K.document.createElement("div"),loadingId:f()},o=e.content,n=e.loadingId;this.htmlElement=o,this.htmlElement.innerHTML=i.html,this.loadingId=n,this.element=Z(t,i,n,o)}else{var s={canvas:K.document.createElement("canvas"),hooks:$(),loadingId:f()},a=s.canvas,r=s.hooks;n=s.loadingId;this.canvas=a,this.hooks=r,this.loadingId=n,function(t,i,e){t.$store={options:i,animationId:void 0,loadingId:null,model:null,hookCall:j(e)}}(t,i,r),this.element=Z(t,i,n,a),t.$store?B(a.offsetWidth,a.offsetHeight,a,this.options,t):c.error("WebLoading:canvas or ctx null")}},i.prototype.callEvent=function(t){this.hooks&&this.hooks[t].forEach((function(t){t()}))},i}(),Q=window,tt=function(){function i(t){this.options=t,this.extendEl=this.initStyle()}return i.prototype.initStyle=function(){this.extendEl=Q.document.createElement("div");var i=this.options,e="100vw",o="100vh",n="0px";return i&&(this.extendEl.classList.add("wl_"+(i.extendClass||"loading")),i.type===t.LOADING_TYPES.MINI&&(e="180px",o="160px",n="10px"),this.extendEl.style.cssText="\n position:fixed;\n width:".concat(e,";\n height:").concat(o,";\n top:50%;\n left:50%;\n transform:translate(-50%, -50%);\n border-radius: ").concat(n,";\n z-index: ").concat(i.zIndex,";\n box-shadow:\n 2.8px 2.8px 2.2px rgba(0, 0, 0, 0.02),\n 6.7px 6.7px 5.3px rgba(0, 0, 0, 0.028),\n 12.5px 12.5px 10px rgba(0, 0, 0, 0.035),\n 22.3px 22.3px 17.9px rgba(0, 0, 0, 0.042),\n 41.8px 41.8px 33.4px rgba(0, 0, 0, 0.05),\n 100px 100px 80px rgba(0, 0, 0, 0.07)\n ;\n ")),Q.document.body.appendChild(this.extendEl),this.extendEl},i.prototype.getElement=function(){return this.extendEl},i}(),it=window;function et(i){var e=new J(i),o=null;function n(t){if(e.element){var i=e.canvas;e.htmlElement&&(i=e.htmlElement),i?e[t](e.element,i):c.warn("Animation element not found!")}}return{loading:function(i,n){var s=Object.assign(e.options,n);if(!e.loadingId&&!o)if(s.type!==t.LOADING_TYPES.DOM&&(i=new tt(s).getElement()),i){var a=new Promise((function(t){it.setTimeout((function(){t(!0)}),s.notFeel)})),r=new Promise((function(t){o=t}));Promise.race([a,r]).then((function(n){n?e.draw(i):s.type!==t.LOADING_TYPES.DOM&&i.remove(),o=null}))}else c.error("The loading function cannot find an HTMLElement element!")},resize:function(){n("resize")},close:function(){o&&o(!1),n("close")},update:function(t){var i=e.canvas,o=Object.assign(e.options,t),n=e.element;i&&o&&n&&n.$store&&B(i.offsetWidth,i.offsetHeight,i,o,n)},getOptions:function(){return e.options},getLoadingId:function(){return e.loadingId}}}function ot(t,i){return et(Object.assign(s(),i||{},{type:t}))}var nt=window;nt.BaseModel=m,nt.initLoading=function(t){return et(t)},nt.miniLoading=function(i){return ot(t.LOADING_TYPES.MINI,i)},nt.fullLoading=function(i){return ot(t.LOADING_TYPES.FULL,i)},t.BaseModel=m,t.fullLoading=function(i){return ot(t.LOADING_TYPES.FULL,i)},t.initLoading=et,t.miniLoading=function(i){return ot(t.LOADING_TYPES.MINI,i)},Object.defineProperty(t,"__esModule",{value:!0})})); 2 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | web-loading 7 | 8 | 9 | 10 |
11 |
12 |
13 | id: 14 | 001 15 |
16 |
17 | user: 18 | use1 19 |
20 |
21 |
22 | 23 | 57 | 58 | 85 | 86 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | if (process.env.NODE_ENV === 'production') { 3 | module.exports = require('./dist/web-loading.cjs.min.js') 4 | } else { 5 | module.exports = require('./dist/web-loading.cjs.js') 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "web-loading", 3 | "version": "1.5.2", 4 | "main": "./dist/web-loading.js", 5 | "typings": "./dist/web-loading.d.ts", 6 | "jsdelivr": "./dist/web-loading.js", 7 | "unpkg": "./dist/web-loading.js", 8 | "module": "./dist/web-loading.esm-bundler.js", 9 | "scripts": { 10 | "build": "tsc && rollup -c rollup.config.js && api-extractor run", 11 | "lint:es": "eslint --cache --max-warnings 0 \"src/**/*.{vue,ts,tsx}\" --fix", 12 | "push": "npm run build & npm publish" 13 | }, 14 | "devDependencies": { 15 | "@microsoft/api-extractor": "^7.35.2", 16 | "@rollup/plugin-commonjs": "^21.0.3", 17 | "@rollup/plugin-inject": "^4.0.4", 18 | "@rollup/plugin-json": "^4.1.0", 19 | "@rollup/plugin-node-resolve": "^13.1.3", 20 | "@rollup/plugin-replace": "^4.0.0", 21 | "@typescript-eslint/eslint-plugin": "^5.59.5", 22 | "eslint": "^8.27.0", 23 | "eslint-config-prettier": "^8.5.0", 24 | "eslint-plugin-prettier": "^4.2.1", 25 | "rollup": "^2.70.1", 26 | "rollup-plugin-terser": "^7.0.2", 27 | "ts-loader": "^9.4.2", 28 | "typescript": "^4.6.4" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "https://github.com/tommyrunner/web-loading.git" 33 | }, 34 | "dependencies": { 35 | "tslib": "^2.5.0" 36 | }, 37 | "keywords": [ 38 | "Vue3", 39 | "HTML", 40 | "Canvas", 41 | "Loading" 42 | ], 43 | "peerDependencies": { 44 | "vue": "^3.0.0" 45 | }, 46 | "author": "tommyrunner", 47 | "license": "ISC" 48 | } 49 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'rollup' 2 | import { join } from 'path' 3 | 4 | import { terser as rollupTerser } from 'rollup-plugin-terser' 5 | import rollupJSON from '@rollup/plugin-json' 6 | import rollupCommonJS from '@rollup/plugin-commonjs' 7 | import rollupReplace from '@rollup/plugin-replace' 8 | import rollupNodeResolve from '@rollup/plugin-node-resolve' 9 | 10 | /** 11 | * @typedef {'umd' | 'cjs' | 'esm-bundler'} OutputFormat 12 | */ 13 | 14 | /** 15 | * @param {OutputFormat} format 16 | * @param {boolean} minify 17 | * @returns {string} 18 | */ 19 | function replaceProcessNodeEnv(format, minify) { 20 | switch (format) { 21 | case 'umd': 22 | case 'cjs': 23 | return minify ? '"production"' : '"development"' 24 | case 'esm-bundler': 25 | return 'process.env.NODE_ENV' 26 | default: 27 | throw new TypeError(`Unsupport format: ${format}`) 28 | } 29 | } 30 | 31 | /** 32 | * @param {OutputFormat} format 33 | * @param {boolean} minify 34 | * @returns {string} 35 | */ 36 | function replaceDev(format, minify) { 37 | switch (format) { 38 | case 'umd': 39 | case 'cjs': 40 | return minify ? 'false' : 'true' 41 | case 'esm-bundler': 42 | return 'process.env.NODE_ENV !== "production"' 43 | default: 44 | throw new TypeError(`Unsupport format: ${format}`) 45 | } 46 | } 47 | 48 | /** 49 | * @param {OutputFormat} format 50 | * @param {boolean} minify 51 | * @returns {import('rollup').RollupOptions} 52 | */ 53 | function createOption(format, minify) { 54 | const name = `web-loading${format === 'umd' ? '' : `.${format}`}${minify ? '.min' : ''}.js` 55 | return { 56 | input: join(__dirname, 'lib/index.js'), 57 | plugins: [ 58 | rollupNodeResolve({ 59 | mainFields: ['browser', 'module', 'main'] 60 | }), 61 | rollupJSON(), 62 | 63 | rollupReplace({ 64 | preventAssignment: true, 65 | 'process.env.NODE_ENV': replaceProcessNodeEnv(format, minify), 66 | __DEV__: replaceDev(format, minify), 67 | __VERSION__: JSON.stringify(require('./package.json').version) 68 | }), 69 | rollupCommonJS({ 70 | transformMixedEsModules: true, 71 | extensions: ['.js', 'jsx', '.ts', '.tsx'] 72 | }), 73 | { 74 | name: 'typescript-class-pure', 75 | transform(code) { 76 | return code.replace(/\/\*\* @class \*\/ \(function/g, '/*#__PURE__*/ (function') 77 | } 78 | }, 79 | ...(minify 80 | ? [ 81 | rollupTerser({ 82 | output: { 83 | comments: false 84 | }, 85 | module: format === 'esm-bundler' 86 | }) 87 | ] 88 | : []) 89 | ], 90 | output: { 91 | file: join(__dirname, 'dist', name), 92 | format: format === 'esm-bundler' ? 'esm' : format, 93 | name: 'rustOption', 94 | exports: 'named' 95 | } 96 | } 97 | } 98 | 99 | export default defineConfig([ 100 | createOption('umd', false), 101 | createOption('umd', true), 102 | createOption('cjs', false), 103 | createOption('cjs', true), 104 | createOption('esm-bundler', false) 105 | ]) 106 | -------------------------------------------------------------------------------- /src/ExtendLoading/index.ts: -------------------------------------------------------------------------------- 1 | import { OptionsType } from '../type' 2 | import { LOADING_TYPES } from '../utils' 3 | const $window = window 4 | /** 5 | * @description 扩展加载类 6 | */ 7 | export default class ExtendLoading { 8 | extendEl: HTMLElement 9 | options?: OptionsType 10 | /** 11 | * @description 构造函数 12 | * @param {OptionsType} [options] - 配置选项 13 | */ 14 | constructor(options?: OptionsType) { 15 | this.options = options 16 | this.extendEl = this.initStyle() 17 | } 18 | /** 19 | * @description 初始化扩展容器元素样式 20 | * @returns {HTMLElement} 扩展容器元素 21 | * @private 22 | */ 23 | private initStyle(): HTMLElement { 24 | this.extendEl = $window.document.createElement('div') 25 | const op = this.options 26 | let w = '100vw', 27 | h = '100vh', 28 | borderRadius = '0px' 29 | if (op) { 30 | this.extendEl.classList.add('wl_' + (op.extendClass || 'loading')) 31 | if (op.type === LOADING_TYPES.MINI) { 32 | w = '180px' 33 | h = '160px' 34 | borderRadius = '10px' 35 | } 36 | this.extendEl.style.cssText = ` 37 | position:fixed; 38 | width:${w}; 39 | height:${h}; 40 | top:50%; 41 | left:50%; 42 | transform:translate(-50%, -50%); 43 | border-radius: ${borderRadius}; 44 | z-index: ${op.zIndex}; 45 | box-shadow: 46 | 2.8px 2.8px 2.2px rgba(0, 0, 0, 0.02), 47 | 6.7px 6.7px 5.3px rgba(0, 0, 0, 0.028), 48 | 12.5px 12.5px 10px rgba(0, 0, 0, 0.035), 49 | 22.3px 22.3px 17.9px rgba(0, 0, 0, 0.042), 50 | 41.8px 41.8px 33.4px rgba(0, 0, 0, 0.05), 51 | 100px 100px 80px rgba(0, 0, 0, 0.07) 52 | ; 53 | ` 54 | } 55 | $window.document.body.appendChild(this.extendEl) 56 | return this.extendEl 57 | } 58 | /** 59 | * @description 获取元素 60 | * @returns {HTMLElement} 扩展容器元素 61 | */ 62 | getElement(): HTMLElement { 63 | return this.extendEl 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Webloading/index.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, OptionsType } from '../type' 2 | import type { HooksType } from './type' 3 | import { LOADING_TYPES, getDefOptions, clearAnimationFrame, $Log, HOOKS_CALL_KEY, onTransitionEndEvent } from '../utils' 4 | import drawController from '../draw/index' 5 | import { initStore, initHooksCall, initCanvas, initHtml } from './init' 6 | import { initContentStyle, setupCanvas, clearStyle } from './style' 7 | const $window = window 8 | /** 9 | * @description Web加载类 10 | */ 11 | export default class WebLoading { 12 | // canvas动画元素 13 | canvas: HTMLCanvasElement | null = null 14 | // Html动画元素 15 | htmlElement: HTMLDivElement | null = null 16 | // 动画元素ID 17 | loadingId: string | null = null 18 | // 容器元素 19 | element: ElementType | null = null 20 | // 配置选项 21 | options: Required 22 | // 钩子 23 | hooks: HooksType | null = null 24 | // 大小调整控制 25 | resizeTimeId: number | null = null 26 | /** 27 | * @description 构造函数 28 | * @param {OptionsType} [options] - 配置选项 29 | */ 30 | constructor(options?: OptionsType) { 31 | // 初始化默认配置 32 | this.options = Object.assign(getDefOptions(), options) 33 | } 34 | /** 35 | * @description 重置动画容器大小 36 | * @param {ElementType} element - 容器元素 37 | * @param {HTMLCanvasElement | HTMLDivElement} animaEl - 动画元素 38 | */ 39 | resize(element: ElementType, animaEl: HTMLCanvasElement | HTMLDivElement) { 40 | if (!this.resizeTimeId) 41 | this.resizeTimeId = $window.setTimeout(() => { 42 | const canvas = animaEl as HTMLCanvasElement 43 | let w = element.clientWidth, 44 | h = element.clientHeight 45 | if (canvas.width > element.clientWidth) { 46 | // 收缩时需要计算滚动条 47 | w = element.offsetWidth 48 | h = element.offsetHeight 49 | } 50 | if (this.canvas) { 51 | setupCanvas(canvas, w, h) 52 | if (element.$store) drawController(w, h, canvas, this.options, element) 53 | } else if (this.htmlElement) { 54 | this.htmlElement.style.width = `${w}px` 55 | this.htmlElement.style.height = `${h}px` 56 | } 57 | this.resizeTimeId = null 58 | }, this.options.delayInto) 59 | } 60 | /** 61 | * @description 关闭动画 62 | * @param {ElementType} element - 容器元素 63 | * @param {HTMLCanvasElement | HTMLDivElement} animaEl - 动画元素 64 | */ 65 | close(element: ElementType, animaEl: HTMLCanvasElement | HTMLDivElement) { 66 | const op = this.options 67 | const $store = element.$store 68 | $window.setTimeout( 69 | () => { 70 | // 触发关闭动画 71 | clearStyle(element, op, animaEl) 72 | if (op.type === LOADING_TYPES.DOM && !op.pointerEvents) { 73 | element.style.pointerEvents = 'auto' 74 | } 75 | // 防止二次关闭,如果二次关闭,需要等待上一个动画结束后再清除缓存 76 | onTransitionEndEvent(element, () => { 77 | // 结束canvas动画前需要结束样式 78 | if ($store) { 79 | // 清除模型 80 | $store.model = null 81 | // 关闭前回调 82 | this.callEvent(HOOKS_CALL_KEY.BEFORE_CLOSE) 83 | // 停止animationFrame 84 | if ($store.animationId) clearAnimationFrame($store.animationId) 85 | } 86 | // 如果dom是扩展的,清除父元素(父元素由webLoading创建) 87 | if (op.type !== LOADING_TYPES.DOM) element.remove() 88 | else animaEl.remove() 89 | // 擦除状态 90 | this.loadingId = null 91 | // 关闭后回调 92 | this.callEvent(HOOKS_CALL_KEY.CLOSED) 93 | // 重置钩子 94 | this.hooks = initHooksCall() 95 | }) 96 | }, 97 | // 如果是二次关闭,需要主动添加延迟 98 | $store && !$store.loadingId ? op.delayInto : 0 99 | ) 100 | } 101 | 102 | /** 103 | * @description 绘制动画 104 | * @param {ElementType} element - 容器元素 105 | */ 106 | draw(element: ElementType) { 107 | const op = this.options 108 | // 兼容html 109 | if (op.html) { 110 | // 初始化基础数据 111 | const { content, loadingId } = initHtml() 112 | this.htmlElement = content 113 | this.htmlElement.innerHTML = op.html 114 | this.loadingId = loadingId 115 | // 初始化样式 116 | this.element = initContentStyle(element, op, loadingId, content) 117 | } else { 118 | // 初始化基础数据 119 | const { canvas, hooks, loadingId } = initCanvas() 120 | this.canvas = canvas 121 | this.hooks = hooks 122 | this.loadingId = loadingId 123 | // 初始化存储 124 | initStore(element, op, hooks) 125 | // 初始化样式 126 | this.element = initContentStyle(element, op, loadingId, canvas) 127 | if (element.$store) { 128 | drawController(canvas.offsetWidth, canvas.offsetHeight, canvas, this.options, element) 129 | } else { 130 | $Log.error('WebLoading:canvas or ctx null') 131 | } 132 | } 133 | } 134 | /** 135 | * @description 触发钩子 136 | * @param {HOOKS_CALL_KEY} hooksKey - 钩子键 137 | * @private 138 | */ 139 | private callEvent(hooksKey: HOOKS_CALL_KEY) { 140 | if (this.hooks) 141 | this.hooks[hooksKey].forEach((event: () => void) => { 142 | event() 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/Webloading/init.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, HooksCallType, OptionsType } from '../type' 2 | import type { HooksType } from './type' 3 | import { HOOKS_CALL_KEY, createLoadingId } from '../utils' 4 | const $window = window 5 | /** 6 | * @description 初始化$store 7 | * @param {ElementType} element - 容器元素 8 | * @param {OptionsType} options - 配置选项 9 | * @param {HooksType} hooks - 钩子函数 10 | */ 11 | export function initStore(element: ElementType, options: OptionsType, hooks: HooksType) { 12 | // 存储状态 13 | element.$store = { 14 | options, 15 | animationId: undefined, 16 | loadingId: null, 17 | model: null, 18 | hookCall: initStoreHooksCall(hooks) 19 | } 20 | } 21 | /** 22 | * @description 初始化钩子调用 23 | * @returns {HooksType} 钩子类型对象 24 | */ 25 | export function initHooksCall(): HooksType { 26 | return { 27 | [HOOKS_CALL_KEY.BEFORE_CLOSE]: [], 28 | [HOOKS_CALL_KEY.CLOSED]: [] 29 | } 30 | } 31 | /** 32 | * @description 初始化存储钩子调用 33 | * @param {HooksType} hooks - 钩子 34 | * @returns {HooksCallType} 钩子调用类型 35 | */ 36 | export function initStoreHooksCall(hooks: HooksType): HooksCallType { 37 | return { 38 | [HOOKS_CALL_KEY.BEFORE_CLOSE]: (fun: (params?: any) => any) => { 39 | hooks[HOOKS_CALL_KEY.BEFORE_CLOSE].push(fun) 40 | }, 41 | [HOOKS_CALL_KEY.CLOSED]: (fun: (params?: any) => any) => { 42 | hooks[HOOKS_CALL_KEY.CLOSED].push(fun) 43 | } 44 | } 45 | } 46 | /** 47 | * @description 初始化Canvas 48 | * @returns {{canvas: HTMLCanvasElement, hooks: HooksType, loadingId: string}} Canvas初始化对象 49 | */ 50 | export function initCanvas() { 51 | return { 52 | canvas: $window.document.createElement('canvas'), 53 | hooks: initHooksCall(), 54 | loadingId: createLoadingId() 55 | } 56 | } 57 | /** 58 | * @description 初始化HTML 59 | * @returns {{content: HTMLDivElement, loadingId: string}} HTML初始化对象 60 | */ 61 | export function initHtml() { 62 | return { 63 | content: $window.document.createElement('div'), 64 | loadingId: createLoadingId() 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Webloading/style.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, OptionsType } from '../type' 2 | import { LOADING_TYPES, toType, onTransitionEndEvent } from '../utils' 3 | const $window = window 4 | /** 5 | * @description 初始化内容样式 6 | * @param {ElementType} element - 容器元素 7 | * @param {Required} op - 配置选项 8 | * @param {string} loadingId - 加载ID 9 | * @param {HTMLCanvasElement | HTMLDivElement} animaEl - 动画元素 10 | * @returns {ElementType} 容器元素 11 | */ 12 | export function initContentStyle( 13 | element: ElementType, 14 | op: Required, 15 | loadingId: string, 16 | animaEl: HTMLCanvasElement | HTMLDivElement 17 | ): ElementType { 18 | // 客户端获取真实宽高,如果启用穿透则获取滚动宽高 19 | const elementW = op.pointerEvents ? element.scrollWidth : element.clientWidth, 20 | elementH = op.pointerEvents ? element.scrollHeight : element.clientHeight, 21 | readElementStyle = $window.getComputedStyle(element), 22 | elementStyle = element.style, 23 | contentStyle = animaEl.style 24 | if (op.type === LOADING_TYPES.DOM && !op.pointerEvents) { 25 | element.style.pointerEvents = 'none' 26 | } 27 | if (!readElementStyle.position || readElementStyle.position === 'static') elementStyle.position = 'relative' 28 | // 初始化canvas样式 29 | animaEl.id = loadingId 30 | contentStyle.opacity = '0' 31 | contentStyle.position = 'absolute' 32 | contentStyle.left = `${op.pointerEvents ? 0 : element.scrollLeft}px` 33 | contentStyle.top = `${op.pointerEvents ? 0 : element.scrollTop}px` 34 | contentStyle.zIndex = op.zIndex 35 | contentStyle.transition = `${op.delayInto / 1000}s ease-in-out` 36 | contentStyle.backgroundColor = op.bgColor 37 | contentStyle.borderRadius = readElementStyle.borderRadius 38 | // 设置canvas大小 39 | if (toType(animaEl) === 'htmlcanvaselement') { 40 | setupCanvas(animaEl as HTMLCanvasElement, elementW, elementH) 41 | } else if (toType(animaEl) === 'htmldivelement') { 42 | // 初始化兼容html样式 43 | contentStyle.width = `${elementW}px` 44 | contentStyle.height = `${elementH}px` 45 | // 居中 46 | contentStyle.display = 'flex' 47 | contentStyle.alignItems = 'center' 48 | contentStyle.justifyContent = 'center' 49 | } 50 | // 注入 51 | element.append(animaEl) 52 | // 触发进入动画 53 | $window.setTimeout(() => (contentStyle.opacity = '1'), 0) 54 | onTransitionEndEvent(element, () => { 55 | // 等待所有元素出现并完成(动画结束) 56 | if (element.$store) element.$store.loadingId = loadingId 57 | }) 58 | return element 59 | } 60 | /** 61 | * @description 处理放大失真,同时更改高度和宽度也会重置canvas的所有内容 62 | * @param {HTMLCanvasElement} canvas - Canvas元素 63 | * @param {number} w - 宽度 64 | * @param {number} h - 高度 65 | */ 66 | export function setupCanvas(canvas: HTMLCanvasElement, w: number, h: number) { 67 | const dpr = $window.devicePixelRatio || 1 68 | canvas.width = w * dpr 69 | canvas.height = h * dpr 70 | canvas.style.width = `${w}px` 71 | canvas.style.height = `${h}px` 72 | } 73 | /** 74 | * @description 清除样式 75 | * @param {ElementType} element - 容器元素 76 | * @param {OptionsType} op - 配置选项 77 | * @param {HTMLCanvasElement | HTMLDivElement} canvas - Canvas或DIV元素 78 | */ 79 | export function clearStyle(element: ElementType, op: OptionsType, canvas: HTMLCanvasElement | HTMLDivElement) { 80 | // 首先视觉过渡 81 | canvas.style.opacity = '0' 82 | // 清除扩展 83 | if (op.type !== LOADING_TYPES.DOM) { 84 | element.style.boxShadow = 'none' 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Webloading/type.ts: -------------------------------------------------------------------------------- 1 | import { HOOKS_CALL_KEY } from '../utils' 2 | /** 3 | * @description 钩子类型 4 | * @template T - 扩展自钩子调用键 5 | */ 6 | export type HooksType = { 7 | [key in T]: Array<(...args: any[]) => void> 8 | } 9 | -------------------------------------------------------------------------------- /src/draw/index.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, OptionsType } from '../type' 2 | import { $Log } from '../utils' 3 | import models from './model' 4 | import BaseModel from './model/BaseModel' 5 | /** 6 | * @description 模型控制器:控制显示的模型 7 | * @param {number} w - 宽度 8 | * @param {number} h - 高度 9 | * @param {HTMLCanvasElement} canvas - Canvas元素 10 | * @param {Required} options - 配置选项 11 | * @param {ElementType} element - 元素 12 | */ 13 | export default function drawController( 14 | w: number, 15 | h: number, 16 | canvas: HTMLCanvasElement, 17 | options: Required, 18 | element: ElementType 19 | ) { 20 | try { 21 | if (!element.$store) return 22 | let storeModel = element.$store.model 23 | if (!storeModel) { 24 | let model = null 25 | if (!options.custom) model = new (models[options.model] as typeof BaseModel)(w, h, canvas, options, element) 26 | else model = new options.custom(w, h, canvas, options, element) 27 | storeModel = model 28 | } else { 29 | // 优化:因为更改高度和宽度后canvas会被重置,需要重新初始化 30 | storeModel.initContextCall(storeModel.modelDefOptions, storeModel.limits, storeModel.modelDefCall) 31 | } 32 | } catch (e) { 33 | $Log.error('draw error(' + e + ')') 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/draw/model/BaseModel.ts: -------------------------------------------------------------------------------- 1 | import type { OptionsType, LimitType, ElementType } from '../../type' 2 | import { isNull, clearAnimationFrame, $Log } from '../../utils' 3 | import { DrawTextParamsType } from '../type' 4 | /** 5 | * @description 基础模型类 6 | * @public 7 | */ 8 | export default class BaseModel { 9 | w: number 10 | h: number 11 | canvas: HTMLCanvasElement 12 | ctx: CanvasRenderingContext2D 13 | options: Required 14 | element: ElementType 15 | // 模型默认选项 16 | modelDefOptions: T | undefined = undefined 17 | // 模型限制 18 | limits: Array | undefined = undefined 19 | // 提供模型初始化的回调函数 20 | modelDefCall: ((model: BaseModel) => void) | undefined = undefined 21 | webLog: $Log = $Log 22 | private stepClear = 1 23 | /** 24 | * @description 自定义基础模型 25 | * @param {number} w - Canvas宽度 26 | * @param {number} h - Canvas高度 27 | * @param {HTMLCanvasElement} canvas - Canvas元素 28 | * @param {Required} options - 配置选项 29 | * @param {ElementType} element - 容器元素 30 | * @param {T} [modelDefOptions] - 模型默认选项(可选) 31 | * @param {Array} [limits] - 模型默认限制(可选) 32 | * @param {(model: BaseModel) => void} [modelDefCall] - 提供模型初始化的回调函数,通常在模型中初始化"canvas"或"画笔"(可选) 33 | */ 34 | constructor( 35 | w: number, 36 | h: number, 37 | canvas: HTMLCanvasElement, 38 | options: Required, 39 | element: ElementType, 40 | modelDefOptions?: T, 41 | limits?: Array, 42 | modelDefCall?: (model: BaseModel) => void 43 | ) { 44 | this.w = w 45 | this.h = h 46 | this.canvas = canvas 47 | // 默认获取2d画笔 48 | this.ctx = canvas.getContext('2d')! 49 | this.options = options 50 | this.element = element 51 | // 初始化模型 52 | this.initContextCall(modelDefOptions, limits, modelDefCall) 53 | } 54 | // 初始化画笔 55 | private _$initBaseContext() { 56 | this.clearRect() 57 | this.ctx.resetTransform() 58 | // 默认主题颜色 59 | const op = this.options, 60 | defW = this.canvas.width, 61 | defH = this.canvas.height 62 | this.ctx.fillStyle = op.themeColor! 63 | this.ctx.strokeStyle = op.themeColor! 64 | this.ctx.shadowColor = op.shadowColor! 65 | this.ctx.font = `${op.fontSize}px ${op.fontFamily} small-caps` 66 | this.ctx.textAlign = 'center' 67 | this.ctx.textBaseline = 'middle' 68 | this.ctx.translate(defW / 2, defH / 2) 69 | // 同步大小处理失真 70 | const dpr = window.devicePixelRatio || 1 71 | this.ctx.scale(dpr, dpr) 72 | this.ctx.save() 73 | } 74 | // 初始化默认事件 75 | private _$initEvent() { 76 | // 关闭前清空画布 77 | if (this.element.$store) 78 | this.element.$store.hookCall.beforeClose(() => { 79 | this.clearRect() 80 | }) 81 | } 82 | /** 83 | * @description 封装requestAnimationFrame触发动画针 84 | * @param {() => void} fun - 触发函数 85 | * @private 86 | */ 87 | private _$animationFrame(fun: () => void) { 88 | const $store = this.element.$store 89 | if (!$store) return 90 | // 兼容处理 91 | if (!window.requestAnimationFrame) { 92 | $store.animationId = window.setInterval(fun, this.options.delay) 93 | } 94 | // 使用时间轴控制触发时间 95 | let endTime = Date.now() + this.options.delay! 96 | fun.call(this) 97 | const run = () => { 98 | if (Date.now() > endTime) { 99 | fun.call(this) 100 | endTime = Date.now() + this.options.delay! 101 | } 102 | $store.animationId = window.requestAnimationFrame(run) 103 | } 104 | $store.animationId = window.requestAnimationFrame(run) 105 | } 106 | /** 107 | * @description 初始化画笔属性 108 | * @param {T} [modelDefOptions] - 提供模型初始化的选项 109 | * @param {Array} [limits] - 提供模型初始化的限制 110 | * @param {(model: BaseModel) => void} [modelDefCall] - 提供模型初始化的回调函数 111 | */ 112 | initContextCall(modelDefOptions?: T, limits?: Array, modelDefCall?: (model: BaseModel) => void) { 113 | // 初始化基础点上下文 114 | this._$initBaseContext() 115 | // 初始化基础钩子事件 116 | this._$initEvent() 117 | // 初始化模型选项 118 | if (isNull(modelDefOptions)) { 119 | // 记录选项 120 | this.modelDefOptions = modelDefOptions 121 | this.options = Object.assign(modelDefOptions, this.options) 122 | if (this.element.$store) this.element.$store.options = this.options 123 | // 判断属性值是否需要限制(仅用于提示) 124 | if (limits && limits.length && this.options.toast) { 125 | limits.forEach((l: LimitType) => { 126 | const mayKey = this.options[l.key as keyof typeof this.options] 127 | if (isNull(mayKey) && !l.limit(mayKey)) $Log.warn(l.message) 128 | }) 129 | } 130 | } 131 | if (isNull(limits)) this.limits = limits 132 | if (isNull(modelDefCall)) { 133 | // this.modelDefCall = modelDefCall 134 | modelDefCall.call(this, this) 135 | } 136 | } 137 | /** 138 | * @description 开始动画 139 | * @param {() => void} fun - 动画函数 140 | */ 141 | run(fun: () => void) { 142 | // 如果已经处于加载状态,无需重新实例化 143 | const $store = this.element.$store 144 | if ($store && $store.animationId) this.clearAnimationFrame($store.animationId) 145 | this._$animationFrame(fun) 146 | } 147 | /** 148 | * @description 取消animationFrame动画针 149 | * @param {number} id - 动画ID 150 | */ 151 | clearAnimationFrame(id: number) { 152 | clearAnimationFrame(id) 153 | } 154 | /** 155 | * @description 清空画布 156 | * @param {number} [x] - x坐标 157 | * @param {number} [y] - y坐标 158 | * @param {number} [w_r] - 宽度或半径 159 | * @param {number} [h] - 高度 160 | */ 161 | clearRect(x?: number, y?: number, w_r?: number, h?: number) { 162 | const defW = this.canvas.width, 163 | defH = this.canvas.height 164 | // 因为起点已设置为中心,需要扩展 165 | if (isNull(x) && isNull(y) && isNull(w_r) && isNull(h)) { 166 | this.ctx.clearRect(x, y, w_r, h) 167 | } 168 | // 清空圆形区域 169 | else if (isNull(x) && isNull(y) && isNull(w_r) && !isNull(h)) { 170 | const calcWidth = w_r - this.stepClear 171 | const calcHeight = Math.sqrt(w_r * w_r - calcWidth * calcWidth) 172 | const posX = x - calcWidth 173 | const posY = y - calcHeight 174 | const widthX = 2 * calcWidth 175 | const heightY = 2 * calcHeight 176 | if (this.stepClear <= w_r) { 177 | this.ctx.clearRect(posX, posY, widthX, heightY) 178 | this.stepClear += 1 179 | this.clearRect(x, y, w_r) 180 | } else { 181 | this.stepClear = 1 182 | } 183 | } else this.ctx.clearRect(-defW, -defH, defW * 2, defH * 2) 184 | } 185 | /** 186 | * @description 绘制圆角矩形 187 | * @param {number} x - x坐标 188 | * @param {number} y - y坐标 189 | * @param {number} w - 宽度 190 | * @param {number} h - 高度 191 | * @param {number} r - 圆角半径 192 | */ 193 | drawRadiusRect(x: number, y: number, w: number, h: number, r: number) { 194 | this.ctx.beginPath() 195 | this.ctx.arc(x + r, y + r, r, 1 * Math.PI, 1.5 * Math.PI) 196 | this.ctx.lineTo(x + w - r, y) 197 | this.ctx.arc(x + w - r, y + r, r, 1.5 * Math.PI, 0) 198 | this.ctx.lineTo(x + w, y + h - r) 199 | this.ctx.arc(x + w - r, y + h - r, r, 0, 0.5 * Math.PI) 200 | this.ctx.lineTo(x + r, y + h) 201 | this.ctx.arc(x + r, y + h - r, r, 0.5 * Math.PI, Math.PI) 202 | this.ctx.lineTo(x, y + r) 203 | this.ctx.closePath() 204 | } 205 | /** 206 | * @description 绘制文本 207 | * @param {DrawTextParamsType} [params] - 文本参数 208 | * DrawTextParamsType: 209 | * esGap?: 额外空隙 210 | * x?: X轴位置 211 | * text?: 文本内容 212 | * textColor?: 文本颜色 213 | */ 214 | drawText(params?: DrawTextParamsType) { 215 | const op = this.options 216 | const pm: Required = Object.assign( 217 | { esGap: op.textGap || 0, x: 0, text: op.text || '', textColor: op.themeColor || 'rgba(64,158,255,1)' }, 218 | params 219 | ) 220 | this.ctx.save() 221 | this.ctx.beginPath() 222 | this.ctx.fillStyle = pm.textColor 223 | this.ctx.fillText(pm.text, pm.x, (op.textGap || 0) + (op.fontSize || 0) + pm.esGap) 224 | this.ctx.closePath() 225 | this.ctx.restore() 226 | } 227 | } 228 | -------------------------------------------------------------------------------- /src/draw/model/Bean.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { BeanOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description Bean模型默认配置选项 6 | */ 7 | const modelDefOptions: BeanOptionsType = { 8 | beanSize: 15, 9 | pointLength: 15 10 | } 11 | 12 | /** 13 | * @description Bean模型限制条件 14 | */ 15 | const limits: Array = [ 16 | { 17 | key: 'pointLength', 18 | message: 'pointLength value >= 5', 19 | limit: (key: any) => { 20 | return key >= 5 21 | } 22 | }, 23 | { 24 | key: 'beanSize', 25 | message: 'beanSize value >= 5', 26 | limit: (key: any) => { 27 | return key >= 5 28 | } 29 | } 30 | ] 31 | /** 32 | * @description Bean类型接口 33 | */ 34 | interface BeanType { 35 | turn: number 36 | state: number 37 | nowX: number 38 | beans: Array 39 | beanState: number 40 | beanAnimaIndex: number 41 | } 42 | /** 43 | * @description Bean模型类 44 | * @extends BaseModel 45 | */ 46 | export default class Bean extends BaseModel { 47 | bean: BeanType 48 | /** 49 | * @description 构造函数 50 | * @param {number} w - 宽度 51 | * @param {number} h - 高度 52 | * @param {HTMLCanvasElement} canvas - Canvas元素 53 | * @param {Required} options - 配置选项 54 | * @param {ElementType} element - 容器元素 55 | */ 56 | constructor( 57 | w: number, 58 | h: number, 59 | canvas: HTMLCanvasElement, 60 | options: Required, 61 | element: ElementType 62 | ) { 63 | super(w, h, canvas, options, element, modelDefOptions, limits) 64 | this.bean = { 65 | turn: 30, 66 | state: 1, 67 | beanState: 1, 68 | nowX: -(this.options.pointLength * this.options.beanSize) / 2 - this.options.beanSize * 3, 69 | beans: Array.from({ length: this.options.pointLength }, () => 1), 70 | beanAnimaIndex: 0 71 | } 72 | this.options.delay = 10 73 | this.run(this.draw) 74 | } 75 | /** 76 | * @description 绘制豆形 77 | */ 78 | draw() { 79 | const op = this.options 80 | this.clearRect() 81 | this.ctx.save() 82 | this.ctx.beginPath() 83 | this.ctx.translate(this.bean.nowX, 0) 84 | this.ctx.arc(0, 0, op.beanSize, ((360 - this.bean.turn) * Math.PI) / 180, (this.bean.turn * Math.PI) / 180, true) 85 | this.ctx.lineTo(0, 0) 86 | this.ctx.fill() 87 | this.ctx.closePath() 88 | this.ctx.restore() 89 | // 绘制点 90 | this.drawPoint() 91 | // 绘制过滤器 92 | this.drawFilter() 93 | // 绘制文本 94 | this.drawText({ esGap: op.beanSize }) 95 | // 控制进程 96 | this.controller() 97 | } 98 | /** 99 | * @description 控制豆形动画 100 | */ 101 | controller() { 102 | const op = this.options 103 | if (this.bean.nowX >= (op.pointLength * op.beanSize) / 2 + op.beanSize * 2) { 104 | this.bean.nowX = -(op.pointLength * op.beanSize) / 2 - op.beanSize * 3 105 | this.bean.beanAnimaIndex = 0 106 | } 107 | if (this.bean.nowX <= -(op.pointLength * op.beanSize) / 2) { 108 | this.bean.beanState = 2 109 | } 110 | if (this.bean.turn <= 0) this.bean.state = 2 111 | if (this.bean.turn >= 30) this.bean.state = 1 112 | if (this.bean.state === 1) this.bean.turn -= 1 113 | if (this.bean.state === 2) this.bean.turn += 1 114 | if (this.bean.beanState === 1) this.bean.nowX -= 1 115 | if (this.bean.beanState === 2) this.bean.nowX += 1 116 | } 117 | /** 118 | * @description 绘制点 119 | */ 120 | drawPoint() { 121 | const op = this.options 122 | this.ctx.save() 123 | this.setShadow() 124 | this.ctx.translate(-(op.pointLength * op.beanSize) / 2, 0) 125 | for (let i = 0; i < op.pointLength && i < this.bean.beanAnimaIndex; i++) { 126 | this.ctx.beginPath() 127 | if (i < this.bean.beanAnimaIndex) this.ctx.arc(op.beanSize * i, 0, op.beanSize / 4, 0, Math.PI * 2) 128 | this.ctx.fill() 129 | this.ctx.closePath() 130 | } 131 | this.bean.beanAnimaIndex += 0.2 132 | this.ctx.restore() 133 | } 134 | /** 135 | * @description 绘制过滤器 136 | */ 137 | drawFilter() { 138 | const op = this.options 139 | // 眼睛 140 | this.clearRect(-op.beanSize / 3 + this.bean.nowX, -op.beanSize / 2, op.beanSize / 4) 141 | // 轨迹 142 | this.clearRect( 143 | -(op.pointLength * op.beanSize) / 2 - op.beanSize / 2 + 0.2, 144 | -this.h, 145 | this.bean.nowX + (op.pointLength * op.beanSize) / 2 - op.beanSize / 2, 146 | this.h * 2 147 | ) 148 | // 进入 149 | this.clearRect(-(op.pointLength * op.beanSize) / 2, -this.h, -180, this.h * 2) 150 | // 离开 151 | this.clearRect((op.pointLength * op.beanSize) / 2, -this.h, 180, this.h * 2) 152 | } 153 | /** 154 | * @description 设置阴影 155 | */ 156 | setShadow() { 157 | const op = this.options 158 | this.ctx.shadowOffsetX = op.shadowOffsetX 159 | this.ctx.shadowOffsetY = op.shadowOffsetY 160 | this.ctx.shadowBlur = op.shadowBlur 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/draw/model/Circular.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType } from '../../type' 2 | import type { CircularOptionsType } from '../type' 3 | import { CIRCULAR_ACTION } from '../../utils' 4 | import BaseModel from './BaseModel' 5 | /** 6 | * @description Circular模型默认配置选项 7 | */ 8 | const modelDefOptions: CircularOptionsType = { 9 | arcSize: 8, 10 | arcGap: 2, 11 | arcColors: ['#ec7546', '#8364a4', '#ff6c6e', '#5bc6ab'], 12 | action: CIRCULAR_ACTION.COLLISION 13 | } 14 | 15 | /** 16 | * @description 碰撞类型接口 17 | */ 18 | interface CollisionType { 19 | key: number 20 | state: boolean 21 | y: number 22 | x: number 23 | } 24 | /** 25 | * @description 圆形模型类 26 | * @extends BaseModel 27 | */ 28 | export default class Circular extends BaseModel { 29 | collisionPoint: Array 30 | turn: number 31 | /** 32 | * @description 构造函数 33 | * @param {number} w - 宽度 34 | * @param {number} h - 高度 35 | * @param {HTMLCanvasElement} canvas - Canvas元素 36 | * @param {Required} options - 配置选项 37 | * @param {ElementType} element - 容器元素 38 | */ 39 | constructor( 40 | w: number, 41 | h: number, 42 | canvas: HTMLCanvasElement, 43 | options: Required, 44 | element: ElementType 45 | ) { 46 | super(w, h, canvas, options, element, modelDefOptions) 47 | // 初始化数据 48 | const op = this.options 49 | const gap = op.arcSize * 2 + op.arcGap 50 | this.collisionPoint = [ 51 | { 52 | key: 0, 53 | state: false, 54 | y: -gap, 55 | x: 0 56 | }, 57 | { 58 | key: 1, 59 | state: false, 60 | y: gap, 61 | x: 0 62 | }, 63 | { 64 | key: 2, 65 | state: false, 66 | y: 0, 67 | x: gap 68 | }, 69 | { 70 | key: 3, 71 | state: false, 72 | y: 0, 73 | x: -gap 74 | } 75 | ] 76 | this.turn = 0 77 | this.run(this.draw) 78 | } 79 | /** 80 | * @description 绘制圆形 81 | */ 82 | draw() { 83 | this.clearRect() 84 | this.ctx.save() 85 | const op = this.options 86 | // 控制进程 87 | if (op.action === CIRCULAR_ACTION.COLLISION) this.controller() 88 | // 旋转 89 | else if (op.action === CIRCULAR_ACTION.ROTATE) { 90 | this.ctx.rotate((this.turn * Math.PI) / 180) 91 | this.turn += 10 92 | } 93 | this.drawCircular() 94 | this.ctx.restore() 95 | this.drawText({ esGap: op.arcSize * 4 + op.arcGap * 2 }) 96 | } 97 | /** 98 | * @description 控制圆形动画 99 | */ 100 | controller() { 101 | const op = this.options 102 | this.collisionPoint.forEach((cp: CollisionType) => { 103 | let key: 'y' | 'x' = 'y' 104 | switch (cp.key) { 105 | case 0: 106 | key = 'y' 107 | break 108 | case 1: 109 | key = 'y' 110 | break 111 | case 2: 112 | key = 'x' 113 | break 114 | case 3: 115 | key = 'x' 116 | break 117 | } 118 | const gap = op.arcSize * 2 + op.arcGap 119 | if (cp[key] === -gap) cp.state = true 120 | if (cp[key] === gap) cp.state = false 121 | if (cp.state) cp[key]++ 122 | else cp[key]-- 123 | }) 124 | } 125 | /** 126 | * @description 绘制圆形元素 127 | */ 128 | drawCircular() { 129 | const op = this.options 130 | this.collisionPoint.forEach((cp: CollisionType, index: number) => { 131 | this.ctx.save() 132 | this.ctx.beginPath() 133 | if (op.arcColors[index]) { 134 | const color = op.arcColors[index] 135 | this.setShadow(color) 136 | this.ctx.fillStyle = color 137 | } 138 | this.ctx.arc(cp.x, cp.y, op.arcSize, 0, Math.PI * 2) 139 | this.ctx.fill() 140 | this.ctx.closePath() 141 | this.ctx.restore() 142 | }) 143 | } 144 | /** 145 | * @description 设置阴影 146 | * @param {string} [color] - 阴影颜色 147 | */ 148 | setShadow(color?: string) { 149 | const op = this.options 150 | color && (this.ctx.shadowColor = color) 151 | this.ctx.shadowOffsetX = op.shadowOffsetX 152 | this.ctx.shadowOffsetY = op.shadowOffsetY 153 | this.ctx.shadowBlur = op.shadowBlur 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /src/draw/model/Clock.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { ClockOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description 时钟模型默认配置选项 6 | */ 7 | const modelDefOptions: ClockOptionsType = { 8 | lineCap: 'round', 9 | lineWidth: 2, 10 | lineColors: ['#d4d4d4', '#06ab2d', '#8a0303'], 11 | clockSize: 15, 12 | clockGap: 4, 13 | hLine: true, 14 | mLine: false, 15 | sLine: true, 16 | textTime: '' 17 | } 18 | 19 | /** 20 | * @description 时钟模型限制条件 21 | */ 22 | const limits: Array = [ 23 | { 24 | key: 'lineColors', 25 | message: 'lineColors.length <= 3', 26 | limit: (key: any) => { 27 | return key.length <= 3 28 | } 29 | } 30 | ] 31 | /** 32 | * @description 时钟模型类 33 | * @extends BaseModel 34 | */ 35 | export default class Clock extends BaseModel { 36 | nowTime: number 37 | nowS: number 38 | /** 39 | * @description 构造函数 40 | * @param {number} w - 宽度 41 | * @param {number} h - 高度 42 | * @param {HTMLCanvasElement} canvas - Canvas元素 43 | * @param {Required} options - 配置选项 44 | * @param {ElementType} element - 容器元素 45 | */ 46 | constructor( 47 | w: number, 48 | h: number, 49 | canvas: HTMLCanvasElement, 50 | options: Required, 51 | element: ElementType 52 | ) { 53 | super(w, h, canvas, options, element, modelDefOptions, limits, (model) => { 54 | const op = model.options 55 | model.ctx.lineCap = op.lineCap 56 | model.ctx.lineWidth = op.lineWidth 57 | model.ctx.save() 58 | }) 59 | this.nowTime = -1 60 | this.nowS = 0 61 | this.run(this.draw) 62 | } 63 | /** 64 | * @description 绘制时钟 65 | */ 66 | draw() { 67 | this.clearRect() 68 | // 绘制时钟 69 | this.drawClock() 70 | } 71 | /** 72 | * @description 绘制时钟元素 73 | */ 74 | drawClock() { 75 | const op = this.options 76 | const s = new Date().getSeconds() 77 | const m = new Date().getMinutes() 78 | const h = new Date().getHours() 79 | // 顶部 80 | this.ctx.save() 81 | this.ctx.beginPath() 82 | this.setShadow() 83 | this.ctx.moveTo(-5, -(op.clockSize + op.clockGap)) 84 | this.ctx.lineTo(5, -(op.clockSize + op.clockGap)) 85 | this.ctx.stroke() 86 | this.ctx.closePath() 87 | // 外圆 88 | this.ctx.beginPath() 89 | this.setShadow() 90 | this.ctx.arc(0, 0, op.clockSize, 0, Math.PI * 2) 91 | this.ctx.stroke() 92 | this.ctx.closePath() 93 | this.ctx.restore() 94 | // 刻度 95 | this.ctx.save() 96 | for (let i = 0; i < 12; i++) { 97 | this.ctx.beginPath() 98 | this.ctx.rotate(((360 / 12) * Math.PI) / 180) 99 | this.ctx.moveTo(op.clockSize - op.clockGap, 0) 100 | this.ctx.lineTo(op.clockSize - op.clockGap, 0) 101 | this.ctx.stroke() 102 | this.ctx.closePath() 103 | } 104 | this.ctx.restore() 105 | // 时针 106 | if (op.hLine) { 107 | this.ctx.save() 108 | this.ctx.beginPath() 109 | this.ctx.lineWidth = op.lineWidth * 1.6 110 | if (op.lineColors[0]) this.ctx.strokeStyle = op.lineColors[0] 111 | // 初始化点 112 | this.ctx.rotate((-90 * Math.PI) / 180) 113 | this.ctx.rotate((((h * 360) / 60) * Math.PI) / 180) 114 | this.ctx.moveTo(-1, 0) 115 | this.ctx.lineTo(op.clockSize / 2, 0) 116 | this.ctx.stroke() 117 | this.ctx.closePath() 118 | this.ctx.restore() 119 | } 120 | // 分针 121 | if (op.mLine) { 122 | this.ctx.save() 123 | this.ctx.beginPath() 124 | if (op.lineColors[1]) this.ctx.strokeStyle = op.lineColors[1] 125 | this.ctx.lineWidth = op.lineWidth * 1.2 126 | // 初始化点 127 | this.ctx.rotate((-90 * Math.PI) / 180) 128 | this.ctx.rotate((((m * 360) / 60) * Math.PI) / 180) 129 | this.ctx.moveTo(-1, 0) 130 | this.ctx.lineTo(op.clockSize / 2 + op.clockGap, 0) 131 | this.ctx.stroke() 132 | this.ctx.closePath() 133 | this.ctx.restore() 134 | } 135 | // 秒针 136 | if (op.sLine) { 137 | this.ctx.save() 138 | this.ctx.beginPath() 139 | if (op.lineColors[2]) this.ctx.strokeStyle = op.lineColors[2] 140 | this.ctx.rotate((-90 * Math.PI) / 180) 141 | this.ctx.rotate((((s * 360) / 60) * Math.PI) / 180) 142 | this.ctx.moveTo(-1, 0) 143 | this.ctx.lineTo(op.clockSize - op.clockGap, 0) 144 | this.ctx.stroke() 145 | this.ctx.closePath() 146 | this.ctx.restore() 147 | if (this.nowS !== s) this.nowTime++ 148 | this.nowS = s 149 | } 150 | if (op.textTime === 'time') op.text = `${h} : ${m} : ${s}` 151 | if (op.textTime === 's') op.text = this.nowTime + 's' 152 | this.drawText({ esGap: op.clockSize * 2 }) 153 | } 154 | /** 155 | * @description 设置阴影 156 | */ 157 | setShadow() { 158 | const op = this.options 159 | this.ctx.shadowOffsetX = op.shadowOffsetX 160 | this.ctx.shadowOffsetY = op.shadowOffsetY 161 | this.ctx.shadowBlur = op.shadowBlur 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /src/draw/model/Gear.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { GearOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description 齿轮模型默认配置选项 6 | */ 7 | const modelDefOptions: GearOptionsType = { 8 | lineStart: 10, 9 | lineEnd: 16, 10 | lineStartSkew: 0, 11 | lineEndSkew: 0, 12 | lineWidth: 4, 13 | lineCap: 'round', 14 | lineNum: 10, 15 | direction: true 16 | } 17 | /** 18 | * @description 齿轮模型限制条件 19 | */ 20 | const limits: Array = [ 21 | { 22 | key: 'lineNum', 23 | message: 'lineNum value 4-18', 24 | limit: (key: any) => { 25 | return key >= 4 && key <= 18 26 | } 27 | } 28 | ] 29 | /** 30 | * @description 齿轮模型类 31 | * @extends BaseModel 32 | */ 33 | export default class Gear extends BaseModel { 34 | aps: Array 35 | /** 36 | * @description 构造函数 37 | * @param {number} w - 宽度 38 | * @param {number} h - 高度 39 | * @param {HTMLCanvasElement} canvas - Canvas元素 40 | * @param {Required} options - 配置选项 41 | * @param {ElementType} element - 容器元素 42 | */ 43 | constructor( 44 | w: number, 45 | h: number, 46 | canvas: HTMLCanvasElement, 47 | options: Required, 48 | element: ElementType 49 | ) { 50 | // 1.初始化 51 | super(w, h, canvas, options, element, modelDefOptions, limits, (model) => { 52 | // 模型额外初始化回调函数 53 | const op = model.options 54 | model.ctx.lineCap = op.lineCap 55 | model.ctx.lineWidth = op.lineWidth 56 | model.ctx.save() 57 | }) 58 | this.optimization(this.options.textGap + this.options.lineEnd) 59 | // 2.启动动画针并记录状态 60 | this.aps = Array.from({ length: this.options.lineNum }, (_, _index) => _index) 61 | this.run(this.draw) 62 | } 63 | 64 | /** 65 | * @description 绘制齿轮 66 | */ 67 | draw() { 68 | this.clearRect() 69 | // 控制进程 70 | this.controller() 71 | // 绘制齿轮 72 | this.drawGear() 73 | // 绘制文本 74 | const op = this.options 75 | this.drawText({ esGap: op.lineEnd }) 76 | } 77 | /** 78 | * @description 控制齿轮动画 79 | */ 80 | controller() { 81 | const op = this.options 82 | if (op.direction) this.aps = this.aps.map((a) => (a - 1 <= 0 ? this.aps.length - 1 : a - 1)) 83 | else this.aps = this.aps.map((a) => (a + 1 > this.aps.length ? 0 : a + 1)) 84 | } 85 | /** 86 | * @description 绘制齿轮元素 87 | */ 88 | drawGear() { 89 | const op = this.options 90 | this.ctx.save() 91 | // 设置阴影 92 | this.ctx.shadowOffsetX = op.shadowOffsetX 93 | this.ctx.shadowOffsetY = op.shadowOffsetY 94 | this.ctx.shadowBlur = op.shadowBlur 95 | // 绘制加载齿轮 96 | for (let i = 0; i < this.aps.length; i++) { 97 | this.ctx.beginPath() 98 | this.ctx.globalAlpha = this.aps[i] / 10 99 | this.ctx.moveTo(op.lineEndSkew, op.lineStart) 100 | this.ctx.lineTo(op.lineStartSkew, op.lineEnd) 101 | this.ctx.stroke() 102 | this.ctx.closePath() 103 | this.ctx.rotate((2 * Math.PI) / op.lineNum) 104 | } 105 | this.ctx.restore() 106 | } 107 | /** 108 | * @description 优化处理(主要优化默认文本位置) 109 | * @param {number} textY - 文本Y坐标 110 | */ 111 | optimization(textY: number) { 112 | // 如果开启优化(优化字体位置) 113 | if (this.options.optimization) { 114 | // 根据宽高调整 115 | if (textY * 4 > this.h) { 116 | this.options.lineStart = this.h / 6 - 5 117 | this.options.lineEnd = this.h / 6 118 | this.options.textGap = 2 119 | } 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/draw/model/Img.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType } from '../../type' 2 | import type { ImageOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description 图片模型默认配置选项 6 | */ 7 | const modelDefOptions: ImageOptionsType = { 8 | src: 'https://tommyrunner.github.io/web-loading/images/logo.png', 9 | width: 52, 10 | height: 52, 11 | turn: true 12 | } 13 | /** 14 | * @description 图片模型类 15 | * @extends BaseModel 16 | */ 17 | export default class Img extends BaseModel { 18 | img: HTMLImageElement 19 | turn: number 20 | /** 21 | * @description 构造函数 22 | * @param {number} w - 宽度 23 | * @param {number} h - 高度 24 | * @param {HTMLCanvasElement} canvas - Canvas元素 25 | * @param {Required} options - 配置选项 26 | * @param {ElementType} element - 容器元素 27 | */ 28 | constructor( 29 | w: number, 30 | h: number, 31 | canvas: HTMLCanvasElement, 32 | options: Required, 33 | element: ElementType 34 | ) { 35 | super(w, h, canvas, options, element, modelDefOptions) 36 | this.img = new Image() 37 | this.img.src = this.options.src 38 | this.turn = 10 39 | this.img.onload = () => { 40 | this.run(this.draw) 41 | } 42 | } 43 | /** 44 | * @description 绘制图片 45 | */ 46 | draw() { 47 | this.clearRect() 48 | this.drawImg() 49 | const op = this.options 50 | this.drawText({ esGap: op.height / 2 }) 51 | } 52 | /** 53 | * @description 绘制图片元素 54 | */ 55 | drawImg() { 56 | const op = this.options 57 | this.ctx.save() 58 | if (op.turn) this.ctx.rotate((this.turn * Math.PI) / 180) 59 | this.ctx.drawImage(this.img, -op.width / 2, -op.height / 2, op.width, op.height) 60 | this.ctx.closePath() 61 | this.ctx.restore() 62 | this.turn += 10 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/draw/model/Pattern.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { PatternOptionsType } from '../type' 3 | import { getDefOptions, PATTERN_CHART } from '../../utils' 4 | import BaseModel from './BaseModel' 5 | /** 6 | * @description 图案模型默认配置选项 7 | */ 8 | const modelDefOptions: PatternOptionsType = { 9 | charts: [PATTERN_CHART.ARC, PATTERN_CHART.RECT, PATTERN_CHART.TRIANGLE, PATTERN_CHART.HEART, PATTERN_CHART.POLYGON], 10 | chartColors: ['#409EFF', '#67C23A', '#E6A23C', '#F56C6C', '#0960bd'], 11 | maxHeight: 60, 12 | chartSize: 12 13 | } 14 | /** 15 | * @description 图案模型限制条件 16 | */ 17 | const limits: Array = [ 18 | { 19 | key: 'chartSize', 20 | message: 'chartSize value 5-24', 21 | limit: (key: any) => { 22 | return key >= 5 && key <= 24 23 | } 24 | }, 25 | { 26 | key: 'delay', 27 | message: 'Pattern.delay not allowed update', 28 | limit: (key: any) => { 29 | return key === getDefOptions().delay 30 | } 31 | } 32 | ] 33 | /** 34 | * @description 图案类型接口 35 | */ 36 | interface PatternType { 37 | // 当前高度 38 | nowHeight: number 39 | // 当前图形 40 | chart: PATTERN_CHART 41 | // 当前颜色 42 | color: string 43 | // 当前旋转角度 44 | turn: number 45 | // 阴影 46 | shadow: number 47 | // 0: 初始化, 1: 上升, 2: 下降 48 | nowState: number 49 | } 50 | /** 51 | * @description 图案模型类 52 | * @extends BaseModel 53 | */ 54 | export default class Pattern extends BaseModel { 55 | pattern: PatternType 56 | /** 57 | * @description 构造函数 58 | * @param {number} w - 宽度 59 | * @param {number} h - 高度 60 | * @param {HTMLCanvasElement} canvas - Canvas元素 61 | * @param {Required} options - 配置选项 62 | * @param {ElementType} element - 容器元素 63 | */ 64 | constructor( 65 | w: number, 66 | h: number, 67 | canvas: HTMLCanvasElement, 68 | options: Required, 69 | element: ElementType 70 | ) { 71 | super(w, h, canvas, options, element, modelDefOptions, limits, (model) => { 72 | model.options.delay = 10 73 | }) 74 | this.pattern = { 75 | color: this.randomState('chartColors'), 76 | nowHeight: 10, 77 | chart: this.randomState('charts'), 78 | shadow: 0, 79 | nowState: 1, 80 | turn: 0 81 | } 82 | this.run(this.draw) 83 | } 84 | /** 85 | * @description 绘制图案 86 | */ 87 | draw() { 88 | const op = this.options 89 | this.clearRect() 90 | this.ctx.save() 91 | this.ctx.beginPath() 92 | this.ctx.translate(0, this.pattern.nowHeight) 93 | this.ctx.rotate((this.pattern.turn / Math.PI) * 2) 94 | this.ctx.fillStyle = this.pattern.color 95 | this.selectChart(0, 0, op.chartSize) 96 | this.ctx.closePath() 97 | this.ctx.restore() 98 | this.drawShadow() 99 | // 清空隐藏部分 100 | this.clearRect(-this.w, 0, this.w * 2, this.h) 101 | // 控制值变化 102 | this.controller(op) 103 | this.drawText({ textColor: this.pattern.color }) 104 | } 105 | /** 106 | * @description 控制图案动画 107 | * @param {Required} op - 配置选项 108 | */ 109 | controller(op: Required) { 110 | this.pattern.turn += 10 // 角度 111 | // 高度和阴影 112 | if (this.pattern.nowState === 1) { 113 | this.pattern.nowHeight-- 114 | this.pattern.shadow += 0.2 115 | } else if (this.pattern.nowState === 2) { 116 | this.pattern.nowHeight++ 117 | this.pattern.shadow -= 0.2 118 | } 119 | this.pattern.shadow = Math.floor(this.pattern.shadow * 100) / 100 120 | // 速度 121 | if (this.pattern.nowHeight <= -op.chartSize && this.pattern.nowHeight % 8 == 0) { 122 | op.delay += 0.5 123 | op.delay = Math.floor(op.delay * 100) / 100 124 | } 125 | // 范围 126 | if (this.pattern.nowHeight <= -op.maxHeight) { 127 | this.pattern.nowState = 2 128 | } else if (this.pattern.nowHeight >= op.chartSize) { 129 | this.pattern.nowState = 1 130 | op.delay = 10 131 | // 切换图形 132 | this.pattern.chart = this.randomState('charts') 133 | // 切换颜色 134 | this.pattern.color = this.randomState('chartColors') 135 | } 136 | } 137 | /** 138 | * @description 选择图形类型 139 | * @param {number} x - x坐标 140 | * @param {number} y - y坐标 141 | * @param {number} size - 尺寸 142 | */ 143 | selectChart(x: number, y: number, size: number) { 144 | switch (this.pattern.chart) { 145 | case PATTERN_CHART.RECT: 146 | this.drawRect(x, y, size) 147 | break 148 | case PATTERN_CHART.ARC: 149 | this.drawArc(x, y, size) 150 | break 151 | case PATTERN_CHART.TRIANGLE: 152 | this.drawTriangle(x, y, size) 153 | break 154 | case PATTERN_CHART.HEART: 155 | this.drawHeart(x, y, size) 156 | break 157 | case PATTERN_CHART.POLYGON: 158 | this.drawPolygon(x, y, size) 159 | break 160 | } 161 | } 162 | /** 163 | * @description 随机选择状态 164 | * @param {any} key - 键名 165 | * @returns {PATTERN_CHART} 返回随机选择的图形类型 166 | */ 167 | randomState(key: any): PATTERN_CHART { 168 | const op: any = this.options 169 | return op[key][parseInt(String(Math.random() * op[key].length))] 170 | } 171 | /** 172 | * @description 绘制阴影 173 | */ 174 | drawShadow() { 175 | this.ctx.save() 176 | this.ctx.beginPath() 177 | this.setShadow() 178 | this.ctx.globalAlpha = 0.2 179 | this.ctx.strokeStyle = this.pattern.color 180 | this.ctx.moveTo(-this.pattern.shadow / 2, 0) 181 | this.ctx.lineTo(this.pattern.shadow, 0) 182 | this.ctx.stroke() 183 | this.ctx.beginPath() 184 | this.ctx.restore() 185 | } 186 | /** 187 | * @description 绘制矩形 188 | * @param {number} x - x坐标 189 | * @param {number} y - y坐标 190 | * @param {number} size - 尺寸 191 | */ 192 | drawRect(x: number, y: number, size: number) { 193 | this.ctx.save() 194 | this.ctx.beginPath() 195 | this.setShadow() 196 | this.ctx.translate(-size / 2, -size / 2) 197 | this.ctx.fillRect(x, y, size, size) 198 | this.ctx.closePath() 199 | this.ctx.restore() 200 | } 201 | /** 202 | * @description 绘制圆形 203 | * @param {number} x - x坐标 204 | * @param {number} y - y坐标 205 | * @param {number} size - 尺寸 206 | */ 207 | drawArc(x: number, y: number, size: number) { 208 | this.ctx.save() 209 | this.ctx.beginPath() 210 | this.setShadow() 211 | this.ctx.arc(x, y, size / 2, 0, Math.PI * 2) 212 | this.ctx.fill() 213 | this.ctx.closePath() 214 | this.ctx.restore() 215 | } 216 | /** 217 | * @description 绘制三角形 218 | * @param {number} x - x坐标 219 | * @param {number} y - y坐标 220 | * @param {number} size - 尺寸 221 | */ 222 | drawTriangle(x: number, y: number, size: number) { 223 | this.ctx.save() 224 | this.ctx.beginPath() 225 | this.setShadow() 226 | this.ctx.translate(-size / 2, -((size / 2) * Math.sqrt(3)) / 2) 227 | this.ctx.moveTo(x, y) 228 | this.ctx.lineTo(size, 0) 229 | this.ctx.lineTo(size / 2, (size / 2) * Math.sqrt(3)) 230 | this.ctx.lineTo(x, y) 231 | this.ctx.fill() 232 | this.ctx.closePath() 233 | this.ctx.restore() 234 | } 235 | /** 236 | * @description 绘制心形 237 | * @param {number} x - x坐标 238 | * @param {number} y - y坐标 239 | * @param {number} size - 尺寸 240 | */ 241 | drawHeart(x: number, y: number, size: number) { 242 | size = size / 2 243 | this.ctx.save() 244 | this.ctx.beginPath() 245 | this.setShadow() 246 | this.ctx.translate(0, -(size * 2) / 2) 247 | this.ctx.moveTo(x, y) 248 | this.ctx.bezierCurveTo(size / 2, -size, size * 3, -size / 2, y, size * 2) 249 | this.ctx.moveTo(x, y) 250 | this.ctx.bezierCurveTo(-size / 2, -size, -size * 3, -size / 2, y, size * 2) 251 | this.ctx.fill() 252 | this.ctx.closePath() 253 | this.ctx.restore() 254 | } 255 | /** 256 | * @description 绘制多边形 257 | * @param {number} x - x坐标 258 | * @param {number} y - y坐标 259 | * @param {number} size - 尺寸 260 | */ 261 | drawPolygon(x: number, y: number, size: number) { 262 | this.ctx.save() 263 | this.ctx.beginPath() 264 | this.setShadow() 265 | this.ctx.translate(-size / 2, -size / 2) 266 | this.ctx.moveTo(x, y) 267 | this.ctx.lineTo(size, y) 268 | this.ctx.lineTo(size + size / 2, size / 2) 269 | this.ctx.lineTo(size, size / 2 + size / 2) 270 | this.ctx.lineTo(x, size) 271 | this.ctx.lineTo(x - size / 2, size - size / 2) 272 | this.ctx.lineTo(x, y) 273 | this.ctx.fill() 274 | this.ctx.closePath() 275 | this.ctx.restore() 276 | } 277 | 278 | /** 279 | * @description 设置阴影 280 | */ 281 | setShadow() { 282 | const op = this.options 283 | this.ctx.shadowColor = this.pattern.color 284 | this.ctx.shadowOffsetX = op.shadowOffsetX 285 | this.ctx.shadowOffsetY = op.shadowOffsetY 286 | this.ctx.shadowBlur = op.shadowBlur 287 | } 288 | } 289 | -------------------------------------------------------------------------------- /src/draw/model/Ring.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { RingOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description 环形模型默认配置选项 6 | */ 7 | const modelDefOptions: RingOptionsType = { 8 | arcGap: Math.PI / 4, 9 | ringGap: 10, 10 | lineWidth: 2, 11 | ringNum: 2, 12 | radius: 6, 13 | lineCap: 'round', 14 | turn: 10, 15 | ringsTurn: [Math.PI, Math.PI / 4], 16 | direction: true 17 | } 18 | /** 19 | * @description 环形模型限制条件 20 | */ 21 | const limits: Array = [ 22 | { 23 | key: 'ringNum', 24 | message: 'ringNum value 1-10', 25 | limit: (key: any) => { 26 | return key >= 1 && key <= 10 27 | } 28 | }, 29 | { 30 | key: 'ringsTurn', 31 | message: `ringsTurn size ${modelDefOptions.ringNum}`, 32 | limit: (key: any) => { 33 | return key.length <= modelDefOptions.ringNum! 34 | } 35 | } 36 | ] 37 | /** 38 | * @description 环形模型类 39 | * @extends BaseModel 40 | */ 41 | export default class Ring extends BaseModel { 42 | /** 每次旋转的角度(默认每次旋转10) */ 43 | rotate: number 44 | /** 45 | * @description 构造函数 46 | * @param {number} w - 宽度 47 | * @param {number} h - 高度 48 | * @param {HTMLCanvasElement} canvas - Canvas元素 49 | * @param {Required} options - 配置选项 50 | * @param {ElementType} element - 容器元素 51 | */ 52 | constructor( 53 | w: number, 54 | h: number, 55 | canvas: HTMLCanvasElement, 56 | options: Required, 57 | element: ElementType 58 | ) { 59 | super(w, h, canvas, options, element, modelDefOptions, limits, (model) => { 60 | const op = model.options 61 | model.ctx.lineCap = op.lineCap 62 | model.ctx.lineWidth = op.lineWidth 63 | model.ctx.save() 64 | }) 65 | this.rotate = 10 66 | this.run(this.draw) 67 | } 68 | /** 69 | * @description 绘制环形 70 | */ 71 | draw() { 72 | this.clearRect() 73 | this.controller() 74 | const op = this.options 75 | this.drawText({ esGap: op.ringNum * (op.radius + op.ringGap / 2) }) 76 | } 77 | /** 78 | * @description 控制环形动画 79 | */ 80 | controller() { 81 | this.ctx.save() 82 | const op = this.options 83 | const rotate = ((this.rotate * Math.PI) / 180) * (op.direction ? 1 : -1) 84 | this.ctx.rotate(rotate) 85 | this.ctx.shadowOffsetX = op.shadowOffsetX 86 | this.ctx.shadowOffsetY = op.shadowOffsetY 87 | this.ctx.shadowBlur = op.shadowBlur 88 | for (let i = 1; i <= op.ringNum; i++) { 89 | this.drawRing( 90 | op.radius + (i - 1) * op.ringGap, 91 | op.arcGap, 92 | op.ringsTurn && op.ringsTurn.length > 0 ? op.ringsTurn[i - 1] : Math.PI / i 93 | ) 94 | } 95 | this.rotate += op.turn 96 | this.ctx.restore() 97 | } 98 | /** 99 | * @description 绘制单个环形 100 | * @param {number} r - 半径 101 | * @param {number} [arcGap=1] - 弧线间隔 102 | * @param {number} [angle=0] - 角度 103 | */ 104 | drawRing(r: number, arcGap = 1, angle = 0) { 105 | // 第一个弧 106 | this.ctx.beginPath() 107 | this.ctx.arc(0, 0, r, arcGap + angle, Math.PI + angle) 108 | this.ctx.stroke() 109 | this.ctx.closePath() 110 | // 第二个弧 111 | this.ctx.beginPath() 112 | this.ctx.arc(0, 0, r, Math.PI + arcGap + angle, angle) 113 | this.ctx.stroke() 114 | this.ctx.closePath() 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/draw/model/Roll.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType, LimitType } from '../../type' 2 | import type { RollOptionsType } from '../type' 3 | import { getDefOptions, ROLL_CHART } from '../../utils' 4 | import BaseModel from './BaseModel' 5 | /** 6 | * @description 滚动模型默认配置选项 7 | */ 8 | const modelDefOptions: RollOptionsType = { 9 | rollGap: 12, 10 | childNum: 4, 11 | rollSize: 16, 12 | showChild: true, 13 | chart: ROLL_CHART.WHEEL, 14 | windmills: ['#1ab3ea', '#de6834', '#30925d', '#f48ea5'], 15 | windmillPointColor: '#f2c31f', 16 | fixed: false 17 | } 18 | 19 | /** 20 | * @description 滚动模型限制条件 21 | */ 22 | const limits: Array = [ 23 | { 24 | key: 'childNum', 25 | message: 'chartSize value 4-10', 26 | limit: (key: any) => { 27 | return key >= 4 && key <= 10 28 | } 29 | }, 30 | { 31 | key: 'delay', 32 | message: 'Roll.delay not allowed update', 33 | limit: (key: any) => { 34 | return key === getDefOptions().delay 35 | } 36 | } 37 | ] 38 | /** 39 | * @description 子元素类型接口 40 | */ 41 | interface childType { 42 | turn: number 43 | x: number 44 | } 45 | /** 46 | * @description 滚动类型接口 47 | */ 48 | interface RollType { 49 | turn: number 50 | nowX: number 51 | state: number 52 | child: Array 53 | } 54 | /** 55 | * @description 滚动模型类 56 | * @extends BaseModel 57 | */ 58 | export default class Roll extends BaseModel { 59 | Roll: RollType 60 | /** 61 | * @description 构造函数 62 | * @param {number} w - 宽度 63 | * @param {number} h - 高度 64 | * @param {HTMLCanvasElement} canvas - Canvas元素 65 | * @param {Required} options - 配置选项 66 | * @param {ElementType} element - 容器元素 67 | */ 68 | constructor( 69 | w: number, 70 | h: number, 71 | canvas: HTMLCanvasElement, 72 | options: Required, 73 | element: ElementType 74 | ) { 75 | super(w, h, canvas, options, element, modelDefOptions, limits) 76 | this.Roll = { 77 | turn: 1, 78 | nowX: this.options.fixed 79 | ? 0 80 | : (this.options.childNum / 2) * (this.options.rollSize + this.options.rollGap) + this.options.rollGap / 2, 81 | state: 2, 82 | child: [] 83 | } 84 | this.run(this.draw) 85 | } 86 | /** 87 | * @description 绘制滚动模型 88 | */ 89 | draw() { 90 | this.clearRect() 91 | // 地面 92 | this.drawGround() 93 | // 绘制子元素 94 | this.drawChild() 95 | this.ctx.save() 96 | this.ctx.beginPath() 97 | this.ctx.translate(-this.Roll.nowX, 0) 98 | this.ctx.rotate((this.Roll.turn * Math.PI) / 180) 99 | // 绘制图形 100 | this.selectChart() 101 | // 控制进程 102 | this.controller() 103 | this.ctx.restore() 104 | // 绘制文本 105 | const op = this.options 106 | this.drawText({ esGap: op.rollSize }) 107 | } 108 | /** 109 | * @description 选择图形类型 110 | */ 111 | selectChart() { 112 | const op = this.options 113 | switch (op.chart) { 114 | case ROLL_CHART.RECT: 115 | this.drawRect() 116 | break 117 | case ROLL_CHART.WHEEL: 118 | this.drawWheel() 119 | break 120 | case ROLL_CHART.WINDMILL: 121 | this.drawWindmill() 122 | break 123 | } 124 | } 125 | /** 126 | * @description 控制滚动动画 127 | */ 128 | controller() { 129 | const op = this.options 130 | if (this.Roll.state === 1) { 131 | this.Roll.turn -= 10 132 | if (op.delay < 20 && !op.fixed) op.delay += 2 133 | } 134 | if (this.Roll.state === 2) { 135 | this.Roll.turn += 10 136 | if (op.delay > 10 && !op.fixed) op.delay -= 5 137 | } 138 | if (op.fixed) return 139 | if (this.Roll.nowX <= -(op.childNum / 2) * (op.rollSize + op.rollGap / 1.6)) this.Roll.state = 1 140 | if (this.Roll.nowX >= (op.childNum / 2) * (op.rollSize + op.rollGap) + op.rollGap / 2) this.Roll.state = 2 141 | if (this.Roll.state === 1) this.Roll.nowX++ 142 | if (this.Roll.state === 2) this.Roll.nowX-- 143 | 144 | const child = this.Roll.child 145 | if (this.Roll.nowX % (op.rollSize + op.rollGap) == 0 && this.Roll.state === 2) { 146 | child.push({ turn: this.Roll.turn, x: this.Roll.nowX }) 147 | } 148 | if (this.Roll.state === 1 && child[child.length - 1] && child[child.length - 1].x === this.Roll.nowX) { 149 | this.Roll.child.pop() 150 | } 151 | } 152 | /** 153 | * @description 绘制矩形 154 | */ 155 | drawRect() { 156 | const op = this.options 157 | this.ctx.save() 158 | this.setShadow() 159 | this.ctx.translate(-op.rollSize / 2, -op.rollSize / 2) 160 | this.ctx.fillRect(0, 0, op.rollSize, op.rollSize) 161 | this.ctx.restore() 162 | } 163 | /** 164 | * @description 绘制轮子 165 | */ 166 | drawWheel() { 167 | const op = this.options 168 | this.ctx.save() 169 | this.ctx.lineWidth = 4 170 | // 内圆1 171 | this.ctx.beginPath() 172 | this.ctx.arc(0, 0, op.rollSize / 6, 0, Math.PI * 2) 173 | this.ctx.stroke() 174 | this.ctx.closePath() 175 | // 内圆2 176 | this.ctx.beginPath() 177 | this.ctx.arc(0, 0, op.rollSize / 2, 0, Math.PI * 2) 178 | this.ctx.stroke() 179 | this.ctx.closePath() 180 | // 外圆 181 | this.ctx.beginPath() 182 | this.ctx.arc(0, 0, op.rollSize, 0, Math.PI * 2) 183 | this.ctx.stroke() 184 | this.ctx.closePath() 185 | // 轮辐 186 | for (let i = 0; i < 6; i++) { 187 | this.ctx.beginPath() 188 | this.ctx.moveTo(0, op.rollSize / 2) 189 | this.ctx.lineTo(0, op.rollSize) 190 | this.ctx.stroke() 191 | this.ctx.rotate(((360 / 6) * Math.PI) / 180) 192 | this.ctx.closePath() 193 | } 194 | this.ctx.restore() 195 | } 196 | /** 197 | * @description 绘制风车 198 | */ 199 | drawWindmill() { 200 | const op = this.options 201 | this.ctx.save() 202 | for (let i = 0; i < op.windmills.length; i++) { 203 | this.ctx.beginPath() 204 | this.ctx.fillStyle = op.windmills[i] 205 | this.ctx.arc(-op.rollSize / 2, 0, op.rollSize, 0, Math.PI) 206 | this.ctx.fill() 207 | this.ctx.closePath() 208 | this.ctx.rotate(((360 / 4) * Math.PI) / 180) 209 | } 210 | // 上层固定点 211 | this.ctx.beginPath() 212 | this.ctx.fillStyle = op.windmillPointColor 213 | this.ctx.arc(0, 0, op.rollSize / 2, 0, Math.PI * 2) 214 | this.ctx.fill() 215 | this.ctx.restore() 216 | } 217 | /** 218 | * @description 绘制子元素 219 | */ 220 | drawChild() { 221 | const op = this.options 222 | if (!op.showChild) return 223 | this.Roll.child.forEach((c, index) => { 224 | this.ctx.save() 225 | this.ctx.translate(-c.x, 0) 226 | this.ctx.globalAlpha = (index + 1) / 10 227 | this.ctx.rotate((c.turn * Math.PI) / 180) 228 | this.selectChart() 229 | this.ctx.restore() 230 | }) 231 | } 232 | /** 233 | * @description 绘制地面 234 | */ 235 | drawGround() { 236 | const op = this.options 237 | if (op.chart !== ROLL_CHART.WHEEL) return 238 | this.ctx.save() 239 | this.ctx.beginPath() 240 | this.ctx.lineWidth = 3 241 | this.ctx.globalAlpha = 0.03 242 | this.ctx.moveTo(-(op.childNum / 2) * (op.rollSize + op.rollGap / 1.6), op.rollSize + 3) 243 | this.ctx.lineTo((op.childNum / 2) * (op.rollSize + op.rollGap) + op.rollGap / 2, op.rollSize + 3) 244 | this.ctx.stroke() 245 | this.ctx.closePath() 246 | this.ctx.restore() 247 | } 248 | /** 249 | * @description 设置阴影 250 | */ 251 | setShadow() { 252 | const op = this.options 253 | this.ctx.shadowOffsetX = op.shadowOffsetX 254 | this.ctx.shadowOffsetY = op.shadowOffsetY 255 | this.ctx.shadowBlur = op.shadowBlur 256 | } 257 | } 258 | -------------------------------------------------------------------------------- /src/draw/model/Skeleton.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType } from '../../type' 2 | import type { SkeletonOptionsType } from '../type' 3 | import BaseModel from './BaseModel' 4 | /** 5 | * @description 骨架屏模型默认配置选项 6 | */ 7 | const modelDefOptions: SkeletonOptionsType = { 8 | skeletonColor: 'rgb(240, 240, 240)', 9 | skeletonAnimationColor: 'rgb(226, 226, 226)', 10 | radius: 5, 11 | animation: true, 12 | deep: true, 13 | appointElementClass: [] 14 | } 15 | /** 16 | * @description 骨架屏元素类型接口 17 | */ 18 | interface SkeletonType { 19 | element: HTMLElement 20 | title: string 21 | } 22 | /** 23 | * @description 骨架屏模型类 24 | * @extends BaseModel 25 | */ 26 | export default class Skeleton extends BaseModel { 27 | skeleton: Array 28 | colorFlow: number 29 | state: number 30 | /** 31 | * @description 构造函数 32 | * @param {number} w - 宽度 33 | * @param {number} h - 高度 34 | * @param {HTMLCanvasElement} canvas - Canvas元素 35 | * @param {Required} options - 配置选项 36 | * @param {ElementType} element - 容器元素 37 | */ 38 | constructor( 39 | w: number, 40 | h: number, 41 | canvas: HTMLCanvasElement, 42 | options: Required, 43 | element: ElementType 44 | ) { 45 | super(w, h, canvas, options, element, modelDefOptions, [], (model) => { 46 | const op = model.options 47 | // 重新初始化canvas 48 | model.ctx.translate(-model.w / 2, -model.h / 2) 49 | model.canvas.width = model.element.scrollWidth 50 | model.canvas.height = model.element.scrollHeight 51 | model.ctx.fillStyle = op.skeletonColor 52 | }) 53 | this.skeleton = [] 54 | this.colorFlow = 0 55 | this.state = 1 56 | this.controller(this.element.children) 57 | this.run(this.draw) 58 | } 59 | /** 60 | * @description 绘制骨架屏 61 | */ 62 | draw() { 63 | this.clearRect() 64 | this.drawSkeleton() 65 | } 66 | /** 67 | * @description 将元素添加到骨架屏元素列表中 68 | * @param {Element} element - 要添加的元素 69 | */ 70 | addElementToSkeleton(element: Element) { 71 | const op = this.options 72 | const filter = op.appointElementClass 73 | // 如果设置了appointElementClass,只处理具有该类的元素 74 | if (filter && filter.length > 0) { 75 | if (filter.some((c) => element.classList.contains(c))) { 76 | this.skeleton.push({ title: element.nodeName, element: element as HTMLElement }) 77 | } 78 | } else { 79 | this.skeleton.push({ title: element.nodeName, element: element as HTMLElement }) 80 | } 81 | } 82 | 83 | /** 84 | * @description 控制器函数,处理DOM元素 85 | * @param {HTMLCollection} els - HTML元素集合 86 | */ 87 | controller(els: HTMLCollection) { 88 | const op = this.options 89 | for (const e of Array.from(els)) { 90 | // 排除绘制canvas元素 91 | if (this.canvas === e) continue 92 | if (op.deep) { 93 | if (e.children.length <= 0) { 94 | this.addElementToSkeleton(e) 95 | } else { 96 | // 如果指定了appointElementClass,则包含父元素,否则只包含所有根元素 97 | if (op.appointElementClass && op.appointElementClass.length) this.addElementToSkeleton(e) 98 | this.controller(e.children) 99 | } 100 | } else { 101 | this.addElementToSkeleton(e) 102 | } 103 | } 104 | } 105 | /** 106 | * @description 绘制骨架元素 107 | */ 108 | drawSkeleton() { 109 | const op = this.options 110 | const linearGradient = this.ctx.createLinearGradient(0, 0, this.w, this.h) 111 | linearGradient.addColorStop(0, op.skeletonColor) 112 | linearGradient.addColorStop(this.colorFlow, op.skeletonAnimationColor) 113 | linearGradient.addColorStop(1, op.skeletonColor) 114 | if (op.animation) this.ctx.fillStyle = linearGradient 115 | this.skeleton.forEach((s) => { 116 | const el = s.element 117 | // 处理圆角露出问题 118 | this.drawRadiusRect(el.offsetLeft, el.offsetTop, el.offsetWidth, el.offsetHeight, op.radius) 119 | this.ctx.fill() 120 | }) 121 | if (op.animation) { 122 | if (this.colorFlow >= 0.9) this.state = 2 123 | if (this.colorFlow <= 0.1) this.state = 1 124 | if (this.state === 1) this.colorFlow += 0.06 125 | if (this.state === 2) this.colorFlow -= 0.06 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /src/draw/model/Zoom.ts: -------------------------------------------------------------------------------- 1 | import type { ElementType } from '../../type' 2 | import type { ZoomOptionsType } from '../type' 3 | import { ZOOM_ACTION } from '../../utils' 4 | import BaseModel from './BaseModel' 5 | /** 6 | * @description 缩放模型默认配置选项 7 | */ 8 | const modelDefOptions: ZoomOptionsType = { 9 | zoomGap: 10, 10 | maxSize: 16, 11 | zoomNum: 5, 12 | lineWidth: 10, 13 | zoomHeight: 2, 14 | lineCap: 'round', 15 | action: ZOOM_ACTION.SCALE, 16 | direction: true, 17 | zoomColors: [] 18 | } 19 | /** 20 | * @description 列表类型接口 21 | */ 22 | interface ListType { 23 | value: number 24 | state: number 25 | } 26 | /** 27 | * @description 缩放模型限制条件 28 | */ 29 | const limits = [ 30 | { 31 | key: 'lineWidth', 32 | message: 'lineWidth(default:10) <= maxSize(default:16)', 33 | limit: (key: any) => { 34 | return key <= modelDefOptions.maxSize! 35 | } 36 | }, 37 | { 38 | key: 'maxSize', 39 | message: 'lineWidth(default:10) <= maxSize(default:16)', 40 | limit: (key: any) => { 41 | return modelDefOptions.lineWidth! <= key 42 | } 43 | } 44 | ] 45 | /** 46 | * @description 缩放模型类 47 | * @extends BaseModel 48 | */ 49 | export default class Zoom extends BaseModel { 50 | list: Array 51 | zoomIndex: number 52 | /** 53 | * @description 构造函数 54 | * @param {number} w - 宽度 55 | * @param {number} h - 高度 56 | * @param {HTMLCanvasElement} canvas - Canvas元素 57 | * @param {Required} options - 配置选项 58 | * @param {ElementType} element - 容器元素 59 | */ 60 | constructor( 61 | w: number, 62 | h: number, 63 | canvas: HTMLCanvasElement, 64 | options: Required, 65 | element: ElementType 66 | ) { 67 | super(w, h, canvas, options, element, modelDefOptions, limits, (model) => { 68 | const op = model.options 69 | model.ctx.lineCap = op.lineCap 70 | model.ctx.lineWidth = op.lineWidth 71 | // 居中 ((缩放宽度 * 数量+1)+(缩放间隙 * 数量+1))/2, 因为第一个缩放会移动所需的数量+1, 高度/2 72 | model.ctx.translate(-(op.lineWidth * (op.zoomNum + 1) + op.zoomGap * (op.zoomNum + 1)) / 2, -op.zoomHeight / 2) 73 | model.ctx.save() 74 | }) 75 | this.zoomIndex = this.options.direction ? 0 : this.options.zoomNum - 1 76 | this.list = Array.from({ length: this.options.zoomNum }, (_, _index) => 77 | Object.assign({ 78 | value: this.options.lineWidth, 79 | // 0: 初始, 1: 变大, 2: 变小 80 | state: 0 81 | }) 82 | ) 83 | this.run(this.draw) 84 | } 85 | /** 86 | * @description 绘制缩放模型 87 | */ 88 | draw() { 89 | this.clearRect() 90 | // 绘制缩放 91 | this.drawZoom() 92 | const op = this.options 93 | this.drawText({ esGap: op.maxSize, x: (op.lineWidth * (op.zoomNum + 1) + op.zoomGap * (op.zoomNum + 1)) / 2 }) 94 | // 控制进程 95 | this.controller() 96 | } 97 | /** 98 | * @description 控制缩放动画 99 | */ 100 | controller() { 101 | const op = this.options 102 | if (op.direction && this.zoomIndex >= op.zoomNum) this.zoomIndex = 0 103 | else if (op.direction && this.zoomIndex < 0) this.zoomIndex = op.zoomNum - 1 104 | } 105 | /** 106 | * @description 绘制缩放元素 107 | */ 108 | drawZoom() { 109 | const op = this.options 110 | for (let i = 0; i < op.zoomNum; i++) { 111 | // 处理变化 112 | if (this.list[i].state === 1) this.list[i].value += 2 113 | else if (this.list[i].state === 2 && this.list[i].value >= op.lineWidth) this.list[i].value-- 114 | if (op.action === ZOOM_ACTION.SCALE) this.ctx.lineWidth = this.list[i].value 115 | // 状态变化 116 | if (i === this.zoomIndex) { 117 | if (this.list[i].value > op.maxSize) { 118 | this.list[i].state = 2 119 | op.direction 120 | ? this.zoomIndex++ 121 | : this.zoomIndex - 1 >= 0 122 | ? this.zoomIndex-- 123 | : (this.zoomIndex = op.zoomNum - 1) 124 | } 125 | if (this.list[i].value <= op.lineWidth) this.list[i].state = 1 126 | } 127 | // 根据数量绘制 128 | this.ctx.beginPath() 129 | if (op.zoomColors.length > 0 && op.zoomColors[i]) this.ctx.strokeStyle = op.zoomColors[i] 130 | else this.ctx.strokeStyle = op.themeColor 131 | let sH = 0, 132 | eH = op.zoomHeight 133 | if (op.action === ZOOM_ACTION.HEIGHT || op.action === ZOOM_ACTION.WAVE) { 134 | sH = -this.list[i].value 135 | } 136 | if (op.action === ZOOM_ACTION.WAVE) { 137 | eH = -this.list[i].value 138 | } 139 | this.ctx.moveTo((i + 1) * (op.lineWidth + op.zoomGap), sH) 140 | this.ctx.lineTo((i + 1) * (op.lineWidth + op.zoomGap), eH) 141 | this.ctx.stroke() 142 | this.ctx.closePath() 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/draw/model/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description 动态导入所有模型 3 | * @note 动态引入会导致404,因此使用静态导入 4 | */ 5 | import Gear from './Gear' 6 | import Zoom from './Zoom' 7 | import Ring from './Ring' 8 | import Bean from './Bean' 9 | import Clock from './Clock' 10 | import Pattern from './Pattern' 11 | import Roll from './Roll' 12 | import Circular from './Circular' 13 | import Img from './Img' 14 | import Skeleton from './Skeleton' 15 | export default { Gear, Zoom, Ring, Bean, Clock, Pattern, Roll, Circular, Img, Skeleton } 16 | -------------------------------------------------------------------------------- /src/draw/type.ts: -------------------------------------------------------------------------------- 1 | import { OptionsType } from '../type' 2 | import { PATTERN_CHART, ROLL_CHART, ZOOM_ACTION, CIRCULAR_ACTION } from '../utils' 3 | /** 4 | * @description 绘制文本参数类型 5 | * @public 6 | */ 7 | export interface DrawTextParamsType { 8 | esGap?: number 9 | x?: number 10 | text?: string 11 | textColor?: string 12 | } 13 | /** 14 | * @description 齿轮模型配置类型 15 | * @public 16 | */ 17 | export interface GearOptionsType extends OptionsType { 18 | // 线条起始 19 | lineStart?: number 20 | // 线条结束 21 | lineEnd?: number 22 | // 线条起始倾斜度 23 | lineStartSkew?: number 24 | // 线条结束倾斜度 25 | lineEndSkew?: number 26 | // 线条宽度 27 | lineWidth?: number 28 | // 线条样式 29 | lineCap?: CanvasLineCap 30 | // 线条数量 31 | lineNum?: number 32 | // 方向:true-正向,然后逆向 33 | direction?: boolean 34 | } 35 | /** 36 | * @description 环形模型配置类型 37 | * @public 38 | */ 39 | export interface RingOptionsType extends OptionsType { 40 | // 环形间隔 41 | ringGap?: number 42 | // 弧线间隔 43 | arcGap?: number 44 | // 线条宽度 45 | lineWidth?: number 46 | // 环形数量 47 | ringNum?: number 48 | // 半径 49 | radius?: number 50 | // 线条样式 51 | lineCap?: CanvasLineCap 52 | // 旋转角度 53 | turn?: number 54 | // 多个环形的初始角度 55 | ringsTurn?: Array 56 | // 方向:true-正向,然后逆向 57 | direction?: boolean 58 | } 59 | /** 60 | * @description 缩放模型配置类型 61 | * @public 62 | */ 63 | export interface ZoomOptionsType extends OptionsType { 64 | // 缩放最大尺寸 65 | maxSize?: number 66 | // 缩放间距 67 | zoomGap?: number 68 | // 缩放高度 69 | zoomHeight?: number 70 | // 缩放数量 71 | zoomNum?: number 72 | // 自定义缩放颜色 73 | zoomColors?: Array 74 | // 线条样式 75 | lineCap?: CanvasLineCap 76 | // 线条宽度 77 | lineWidth?: number 78 | // 动作类型 79 | action: ZOOM_ACTION 80 | // 方向:true-正向,然后逆向 81 | direction?: boolean 82 | } 83 | /** 84 | * @description 图案模型配置类型 85 | * @public 86 | */ 87 | export interface PatternOptionsType extends OptionsType { 88 | // 支持的图形 89 | charts?: Array 90 | // 图形尺寸大小 91 | chartSize?: number 92 | // 动态颜色 93 | chartColors?: Array 94 | // 最大高度 95 | maxHeight?: number 96 | } 97 | /** 98 | * @description 时钟模型配置类型 99 | * @public 100 | */ 101 | export interface ClockOptionsType extends OptionsType { 102 | // 文本显示模式:time-日期时间,s-秒 103 | textTime?: 'time' | 's' | '' 104 | // 线条颜色 105 | lineColors?: Array 106 | // 线条样式 107 | lineCap?: CanvasLineCap 108 | // 线条宽度 109 | lineWidth?: number 110 | // 时钟尺寸 111 | clockSize?: number 112 | // 时钟间隔 113 | clockGap?: number 114 | // 时分秒指针 115 | hLine?: boolean 116 | mLine?: boolean 117 | sLine?: boolean 118 | } 119 | /** 120 | * @description 豆形模型配置类型 121 | * @public 122 | */ 123 | export interface BeanOptionsType extends OptionsType { 124 | // 豆形尺寸 125 | beanSize?: number 126 | // 豆形中的点数量 127 | pointLength?: number 128 | } 129 | /** 130 | * @description 滚动模型配置类型 131 | * @public 132 | */ 133 | export interface RollOptionsType extends OptionsType { 134 | // 滚动间隔 135 | rollGap?: number 136 | // 滚动尺寸 137 | rollSize?: number 138 | // 显示子元素 139 | showChild?: boolean 140 | // 子元素数量 141 | childNum?: number 142 | // 显示的图形 143 | chart?: ROLL_CHART 144 | // 风车图形时的叶片颜色 145 | windmills?: Array 146 | // 风车中心颜色 147 | windmillPointColor?: string 148 | // 中心是否固定 149 | fixed?: boolean 150 | } 151 | /** 152 | * @description 图片模型配置类型 153 | * @public 154 | */ 155 | export interface ImageOptionsType extends OptionsType { 156 | src?: string 157 | width?: number 158 | height?: number 159 | turn?: boolean 160 | } 161 | /** 162 | * @description 骨架屏模型配置类型 163 | * @public 164 | */ 165 | export interface SkeletonOptionsType extends OptionsType { 166 | skeletonColor?: string 167 | skeletonAnimationColor?: string 168 | radius?: number 169 | animation?: boolean 170 | deep?: boolean 171 | appointElementClass?: Array 172 | } 173 | /** 174 | * @description 圆形模型配置类型 175 | * @public 176 | */ 177 | export interface CircularOptionsType extends OptionsType { 178 | // 弧线尺寸 179 | arcSize?: number 180 | // 弧线间隔 181 | arcGap?: number 182 | // 弧线颜色 183 | arcColors?: Array 184 | // 动作类型 185 | action?: CIRCULAR_ACTION 186 | } 187 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @packageDocumentation 3 | * @description 包文档说明 4 | */ 5 | export { OptionsType, LoadingType, ElementStoreType, ElementType, LimitType, LogConfigType } from './type' 6 | import './main' 7 | export * from './draw/type' 8 | export { 9 | LOADING_TYPES, 10 | LOG_TYPES, 11 | MODEL_TYPES, 12 | HOOKS_CALL_KEY, 13 | ZOOM_ACTION, 14 | PATTERN_CHART, 15 | ROLL_CHART, 16 | CIRCULAR_ACTION 17 | } from './utils' 18 | import BaseModel from './draw/model/BaseModel' 19 | import initLoading, { fullLoading, miniLoading } from './loading' 20 | export { BaseModel, initLoading, fullLoading, miniLoading } 21 | -------------------------------------------------------------------------------- /src/loading.ts: -------------------------------------------------------------------------------- 1 | import WebLoading from './WebLoading/index' 2 | import ExtendLoading from './ExtendLoading' 3 | import type { OptionsType, LoadingType, ElementType } from './type' 4 | import { LOADING_TYPES, getDefOptions, $Log } from './utils' 5 | import drawController from './draw/index' 6 | const $window = window 7 | /** 8 | * @description 初始化加载动画 9 | * @param {OptionsType} options - 配置选项 10 | * @returns {LoadingType} 返回加载操作对象 11 | * @public 12 | */ 13 | export default function initLoading(options?: OptionsType): LoadingType { 14 | const webLoading = new WebLoading(options) 15 | let feelPromiseResolve: ((value: boolean | PromiseLike) => void) | null = null 16 | const resize = () => { 17 | utlWL('resize') 18 | } 19 | const loading = (dom: ElementType, options?: OptionsType) => { 20 | // 保存最后传入的参数 21 | const op = Object.assign(webLoading.options, options) 22 | // 防止重复注册 23 | if (!webLoading.loadingId && !feelPromiseResolve) { 24 | // 创建扩展DOM 25 | if (op.type !== LOADING_TYPES.DOM) { 26 | dom = new ExtendLoading(op).getElement() as ElementType 27 | } 28 | if (!dom) $Log.error('The loading function cannot find an HTMLElement element!') 29 | else { 30 | // 通过Promise.race处理无感加载 31 | const loadingPromise = new Promise((res) => { 32 | // 如果notFeel的时间超过关闭时间,则认为是无感加载 33 | $window.setTimeout(() => { 34 | res(true) 35 | }, op.notFeel) 36 | }) 37 | const feelPromise = new Promise((res) => { 38 | feelPromiseResolve = res 39 | }) 40 | Promise.race([loadingPromise, feelPromise]).then((res) => { 41 | if (res) webLoading.draw(dom) 42 | else { 43 | // 处理扩展DOM 44 | if (op.type !== LOADING_TYPES.DOM) dom.remove() 45 | } 46 | feelPromiseResolve = null 47 | }) 48 | } 49 | } 50 | } 51 | const update = (options?: OptionsType) => { 52 | const canvas = webLoading.canvas 53 | const op = Object.assign(webLoading.options, options) 54 | const element = webLoading.element 55 | if (canvas && op && element && element.$store) 56 | drawController(canvas.offsetWidth, canvas.offsetHeight, canvas, op, element) 57 | } 58 | const close = () => { 59 | feelPromiseResolve && feelPromiseResolve(false) 60 | utlWL('close') 61 | } 62 | // 获取基本信息 63 | const getLoadingId = () => webLoading.loadingId 64 | const getOptions = () => webLoading.options 65 | // WebLoading操作工具函数 66 | function utlWL(key: 'resize' | 'close') { 67 | if (webLoading.element) { 68 | // canvas元素 69 | let temEl: HTMLCanvasElement | HTMLDivElement | null = webLoading.canvas 70 | // html元素 71 | if (webLoading.htmlElement) temEl = webLoading.htmlElement 72 | // 设置 73 | if (temEl) webLoading[key](webLoading.element, temEl) 74 | else $Log.warn('Animation element not found!') 75 | } 76 | } 77 | return { 78 | loading, 79 | resize, 80 | close, 81 | update, 82 | getOptions, 83 | getLoadingId 84 | } 85 | } 86 | /** 87 | * @description 扩展加载方法 88 | * @param {LOADING_TYPES} type - 加载类型 89 | * @param {OptionsType} options - 配置选项 90 | * @returns {LoadingType} 返回加载操作对象 91 | * @private 92 | */ 93 | export function _$extendLoading(type: LOADING_TYPES, options?: OptionsType) { 94 | return initLoading(Object.assign(getDefOptions(), options || {}, { type })) 95 | } 96 | /** 97 | * @description 全屏加载 98 | * @param {OptionsType} options - 配置选项 99 | * @returns {LoadingType} 返回加载操作对象 100 | * @public 101 | */ 102 | export function fullLoading(options?: OptionsType) { 103 | return _$extendLoading(LOADING_TYPES.FULL, options) 104 | } 105 | /** 106 | * @description 迷你加载 107 | * @param {OptionsType} options - 配置选项 108 | * @returns {LoadingType} 返回加载操作对象 109 | * @public 110 | */ 111 | export function miniLoading(options?: OptionsType) { 112 | return _$extendLoading(LOADING_TYPES.MINI, options) 113 | } 114 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import type { OptionsType, WindowType } from './type' 2 | import _$BaseModel from './draw/model/BaseModel' 3 | import { LOADING_TYPES } from './utils' 4 | import _$initLoading, { _$extendLoading } from './loading' 5 | const $window: WindowType = window 6 | $window.BaseModel = _$BaseModel 7 | // 初始化 8 | $window.initLoading = function (options?: OptionsType) { 9 | return _$initLoading(options) 10 | } 11 | // 扩展加载方法 12 | // 移动端 13 | $window.miniLoading = (options?: OptionsType) => { 14 | return _$extendLoading(LOADING_TYPES.MINI, options) 15 | } 16 | // 全屏 17 | $window.fullLoading = (options?: OptionsType) => { 18 | return _$extendLoading(LOADING_TYPES.FULL, options) 19 | } 20 | /** 21 | * @description JS项目单独导入方法 22 | * @public 23 | */ 24 | export default { 25 | initLoading: $window.initLoading, 26 | miniLoading: $window.miniLoading, 27 | fullLoading: $window.fullLoading, 28 | BaseModel: $window.BaseModel 29 | } 30 | -------------------------------------------------------------------------------- /src/type.ts: -------------------------------------------------------------------------------- 1 | import { MODEL_TYPES, LOADING_TYPES, HOOKS_CALL_KEY } from './utils' 2 | import BaseModel from './draw/model/BaseModel' 3 | /** 4 | * @description 配置选项接口 5 | * @public 6 | */ 7 | export interface OptionsType { 8 | // 自定义模型 9 | custom?: typeof BaseModel | null 10 | // 加载启动模式(默认DEF)[只读] 11 | type?: LOADING_TYPES 12 | // mini模式的类名 13 | extendClass?: string | null | undefined 14 | // 模型类型 15 | model?: MODEL_TYPES 16 | // Html加载内容 17 | html?: string 18 | // 文本内容 19 | text?: string 20 | // 文本间距 21 | textGap?: number 22 | // 字体大小 23 | fontSize?: number 24 | // 字体系列 25 | fontFamily?: string 26 | // 延迟时间 27 | delay?: number 28 | // 进入延迟 29 | delayInto?: number 30 | // 无感刷新 31 | notFeel?: number 32 | // 性能优化 33 | optimization?: boolean 34 | // 加载层级 35 | zIndex?: string 36 | // 主题颜色 37 | themeColor?: string 38 | // 背景颜色 39 | bgColor?: string 40 | // 阴影颜色 41 | shadowColor?: string 42 | // 阴影X偏移 43 | shadowOffsetX?: number 44 | // 阴影Y偏移 45 | shadowOffsetY?: number 46 | // 阴影模糊度 47 | shadowBlur?: number 48 | // 事件穿透(DOM模式) 49 | pointerEvents?: boolean 50 | // 显示提示 51 | toast?: boolean 52 | } 53 | /** 54 | * @description 扩展Window类型接口 55 | */ 56 | export interface WindowType extends Window { 57 | BaseModel?: typeof BaseModel 58 | initLoading?: (options: OptionsType) => LoadingType 59 | fullLoading?: (options: OptionsType) => LoadingType 60 | miniLoading?: (options: OptionsType) => LoadingType 61 | } 62 | /** 63 | * @description 加载类型接口 64 | * @public 65 | */ 66 | export interface LoadingType { 67 | loading: (dom: ElementType, options?: OptionsType) => void 68 | resize: () => void 69 | close: () => void 70 | update: (options?: OptionsType) => void 71 | getOptions: () => OptionsType 72 | getLoadingId: () => string | null 73 | } 74 | /** 75 | * @description 钩子调用类型 76 | * 映射键是枚举 77 | */ 78 | export type HooksCallType = { 79 | [key in T]: (...args: any[]) => void 80 | } 81 | /** 82 | * @description 元素存储类型接口 83 | * @public 84 | */ 85 | export interface ElementStoreType { 86 | // 保存最终合并的选项参数 87 | options: OptionsType 88 | // 用于记录动画状态 89 | animationId: number | undefined 90 | // 记录loading元素id 91 | loadingId: string | null 92 | // loading的钩子函数 93 | hookCall: HooksCallType 94 | // 使用中的模型 95 | model: BaseModel | null 96 | } 97 | /** 98 | * @description 元素类型接口 99 | * @public 100 | */ 101 | export interface ElementType extends HTMLElement { 102 | loadingId?: string | null 103 | $store?: ElementStoreType 104 | } 105 | /** 106 | * @description 限制类型接口 107 | * @public 108 | */ 109 | export interface LimitType { 110 | key: string 111 | message: string 112 | limit: (key: any) => boolean 113 | } 114 | /** 115 | * @description 日志配置类型 116 | * @public 117 | */ 118 | export type LogConfigType = { 119 | // 文本颜色 120 | color?: string 121 | // 背景提示颜色 122 | bgColor?: string 123 | } 124 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import { LogConfigType, OptionsType } from './type' 2 | /** 3 | * @description 支持的加载方法 4 | * @public 5 | */ 6 | export enum LOADING_TYPES { 7 | DOM = 'dom', 8 | FULL = 'full', 9 | MINI = 'mini' 10 | } 11 | /** 12 | * @description 支持的模型类型 13 | * @public 14 | */ 15 | export enum MODEL_TYPES { 16 | // 齿轮 17 | GEAR = 'Gear', 18 | // 环形 19 | RING = 'Ring', 20 | // 缩放 21 | ZOOM = 'Zoom', 22 | // 图案 23 | PATTERN = 'Pattern', 24 | // 时钟 25 | CLOCK = 'Clock', 26 | // 豆形 27 | BEAN = 'Bean', 28 | // 滚动 29 | ROLL = 'Roll', 30 | // 圆形 31 | CIRCULAR = 'Circular', 32 | // 图片 33 | IMG = 'Img', 34 | // 骨架屏 35 | SKELETON = 'Skeleton' 36 | } 37 | /** 38 | * @description 返回默认配置 39 | * @returns {Required} 返回默认配置对象 40 | * @public 41 | */ 42 | export function getDefOptions(): Required { 43 | return { 44 | custom: null, 45 | html: '', 46 | type: LOADING_TYPES.DOM, 47 | extendClass: 'extend', 48 | model: MODEL_TYPES.GEAR, 49 | text: '加载中...', 50 | textGap: 8, 51 | fontSize: 12, 52 | fontFamily: 'Microsoft YaHei', 53 | delay: 65, 54 | notFeel: 0, 55 | delayInto: 320, 56 | optimization: false, 57 | zIndex: '2001', 58 | themeColor: 'rgba(64,158,255,1)', 59 | bgColor: 'rgba(0, 0, 0, 0.8)', 60 | shadowColor: 'rgba(64,158,255,0.6)', 61 | shadowOffsetX: 2, 62 | shadowOffsetY: 2, 63 | shadowBlur: 5, 64 | pointerEvents: false, 65 | toast: true 66 | } 67 | } 68 | /** 69 | * @description 钩子调用键枚举 70 | * @public 71 | */ 72 | export enum HOOKS_CALL_KEY { 73 | BEFORE_CLOSE = 'beforeClose', 74 | CLOSED = 'closed' 75 | } 76 | /** 77 | * @description 日志类型枚举 78 | * @public 79 | */ 80 | export enum LOG_TYPES { 81 | INFO = 1, 82 | WARN = 2, 83 | ERROR = 3 84 | } 85 | /** 86 | * @description 日志输出类 87 | * @public 88 | */ 89 | export class $Log { 90 | /** 91 | * @description 输出信息日志 92 | * @param {string} message - 日志内容 93 | */ 94 | static info(message: string) { 95 | this.call(message, LOG_TYPES.INFO) 96 | } 97 | /** 98 | * @description 输出警告日志 99 | * @param {string} message - 日志内容 100 | */ 101 | static warn(message: string) { 102 | this.call(message, LOG_TYPES.WARN) 103 | } 104 | /** 105 | * @description 输出错误日志 106 | * @param {string} message - 日志内容 107 | */ 108 | static error(message: string) { 109 | this.call(message, LOG_TYPES.ERROR) 110 | } 111 | /** 112 | * @description 调用日志输出 113 | * @param {string} message - 日志内容 114 | * @param {LOG_TYPES} type - 日志类型 115 | * @param {LogConfigType} config - 日志配置 116 | */ 117 | static call( 118 | message: string, 119 | type: LOG_TYPES = LOG_TYPES.INFO, 120 | config: LogConfigType = { 121 | color: getDefOptions().themeColor, 122 | bgColor: getDefOptions().bgColor 123 | } 124 | ) { 125 | let bgColor = config.bgColor 126 | // 警告颜色不能被更改 127 | if (type === 2) bgColor = '#fffbe5' 128 | // 错误颜色不能被更改 129 | if (type === 3) bgColor = '#fff0f0' 130 | const style = ` 131 | background:${bgColor}; 132 | font-size:14px; 133 | color:${config.color}; 134 | padding: 4px; 135 | border: 1px solid;` 136 | console.log(`%c web-loading:${message} `, style) 137 | } 138 | } 139 | /** 140 | * @description 判断值是否为空 141 | * @param {any} value - 待判断的值 142 | * @returns {boolean} 返回判断结果 143 | * @public 144 | */ 145 | export function isNull(value: any): value is boolean | ((...args: any[]) => any) { 146 | switch (toType(value)) { 147 | case 'object': 148 | return Object.keys(value).length > 0 149 | case 'array': 150 | return value.length > 0 151 | case 'undefined': 152 | return value !== undefined 153 | case 'null': 154 | return value !== null 155 | default: 156 | return value !== undefined 157 | } 158 | } 159 | /** 160 | * @description 清除动画帧 161 | * @param {number} id - 动画帧ID 162 | * @public 163 | */ 164 | export function clearAnimationFrame(id: number) { 165 | if (!window.requestAnimationFrame) { 166 | window.clearInterval(id) 167 | } else { 168 | window.cancelAnimationFrame(id) 169 | } 170 | } 171 | /** 172 | * @description 获取数据类型 173 | * @param {any} key - 待检测的值 174 | * @returns {string | 'not-type'} 返回类型字符串 175 | * @public 176 | */ 177 | export function toType(key: any): string | 'not-type' { 178 | try { 179 | const type = Object.prototype.toString.call(key) 180 | const t1 = type.split(' ')[1] 181 | const t2 = t1.split(']')[0] 182 | return t2.toLowerCase() 183 | } catch (e) { 184 | return 'not-type' 185 | } 186 | } 187 | /** 188 | * @description 监听动画结束事件 189 | * @param {HTMLElement} el - 元素 190 | * @param {() => void} fun - 执行函数 191 | * @public 192 | */ 193 | export function onTransitionEndEvent(el: HTMLElement, fun: () => void) { 194 | let transitionsName: string | null = null 195 | const transitions: { [key in string]: string } = { 196 | transition: 'transitionend', 197 | OTransition: 'oTransitionEnd', 198 | MozTransition: 'transitionend', 199 | WebkitTransition: 'webkitTransitionEnd' 200 | } 201 | for (const t in transitions) { 202 | if (el.style[t as any] !== undefined) { 203 | transitionsName = transitions[t] 204 | break 205 | } 206 | } 207 | if (!transitionsName) { 208 | fun() 209 | } else { 210 | const transitionFun = () => { 211 | fun() 212 | el.removeEventListener(transitionsName as string, transitionFun) 213 | } 214 | el.addEventListener(transitionsName, transitionFun) 215 | } 216 | } 217 | /** 218 | * @description 创建唯一的loadingID 219 | * @returns {string} 返回生成的唯一ID 220 | * @public 221 | */ 222 | export function createLoadingId() { 223 | let id = String(Date.now()) 224 | if (window.crypto && window.crypto.randomUUID) id = window.crypto.randomUUID() 225 | return 'wl_' + id.replace(/-/g, '') 226 | } 227 | /** 228 | * @description 缩放动作枚举 229 | * @public 230 | */ 231 | export enum ZOOM_ACTION { 232 | SCALE = 'scale', 233 | WAVE = 'wave', 234 | HEIGHT = 'height' 235 | } 236 | /** 237 | * @description 图案类型枚举 238 | * @public 239 | */ 240 | export enum PATTERN_CHART { 241 | RECT = 'rect', 242 | ARC = 'arc', 243 | TRIANGLE = 'triangle', 244 | HEART = 'heart', 245 | POLYGON = 'polygon' 246 | } 247 | /** 248 | * @description 滚动图表类型枚举 249 | * @public 250 | */ 251 | export enum ROLL_CHART { 252 | RECT = 'rect', 253 | WHEEL = 'wheel', 254 | WINDMILL = 'windmill' 255 | } 256 | /** 257 | * @description 圆形动作枚举 258 | * @public 259 | */ 260 | export enum CIRCULAR_ACTION { 261 | COLLISION = 'collision', 262 | ROTATE = 'rotate' 263 | } 264 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "allowJs": true, 5 | "noUnusedLocals": true, 6 | "noUnusedParameters": true, 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "noImplicitReturns": true, 10 | "moduleResolution": "node", 11 | "resolveJsonModule": true, 12 | "importHelpers": true, 13 | "noEmitHelpers": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "compilerOptions": { 4 | "target": "ES5", 5 | "module": "ESNext", 6 | "declaration": true, 7 | "outDir": "lib" 8 | }, 9 | "include": [ 10 | "./src/**/*" 11 | ] 12 | } 13 | --------------------------------------------------------------------------------