├── .babelrc
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── dist
├── lucky-canvas.cjs.js
├── lucky-canvas.cjs.min.js
├── lucky-canvas.umd.js
└── lucky-canvas.umd.min.js
├── examples
├── dev.js
├── dev.js.map
├── ggk
│ └── index.html
├── index.html
├── ymc
│ ├── img
│ │ ├── 0.png
│ │ ├── 1.png
│ │ ├── 2.png
│ │ ├── 3.png
│ │ ├── 4.png
│ │ ├── 5.png
│ │ ├── 6.png
│ │ ├── 7.png
│ │ └── button.png
│ └── index.html
└── yyjk
│ ├── img
│ ├── 0.png
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ ├── 5.png
│ ├── 6.png
│ ├── 7.png
│ ├── btn.png
│ └── button.png
│ └── index.html
├── index.js
├── logo.png
├── package.json
├── rollup.config.build.js
├── rollup.config.dev.js
├── src
├── index.ts
├── lib
│ ├── card.ts
│ ├── grid.ts
│ ├── lucky.ts
│ └── wheel.ts
├── observer
│ ├── array.ts
│ ├── dep.ts
│ ├── index.ts
│ ├── utils.ts
│ └── watcher.ts
├── types
│ ├── card.ts
│ ├── grid.ts
│ ├── index.ts
│ └── wheel.ts
└── utils
│ ├── index.ts
│ ├── math.ts
│ ├── polyfill.js
│ └── tween.ts
├── tsconfig.json
├── umd.min.js
├── web.svg
└── wx.jpg
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env", {
5 | "modules": false
6 | }
7 | ]
8 | ]
9 | }
10 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "parser": "@typescript-eslint/parser",
8 | // "plugins": ['prettier'],
9 | "extends": "eslint:recommended",
10 | "globals": {
11 | "Atomics": "readonly",
12 | "SharedArrayBuffer": "readonly",
13 | "ENV": true
14 | },
15 | "parserOptions": {
16 | "ecmaVersion": 2018,
17 | "sourceType": "module"
18 | },
19 | "rules": {
20 | "linebreak-style": 'off'
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | yarn.lock
4 | package-lock.json
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 |
15 | # Editor directories and files
16 | .idea
17 | .vscode
18 | *.suo
19 | *.ntvs*
20 | *.njsproj
21 | *.sln
22 | *.sw?
23 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | *
--------------------------------------------------------------------------------
/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 [2021] [Li Dong Qi]
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 | ```diff
2 | - 该库已迁移到 https://github.com/LuckDraw/lucky-canvas
3 | ```
4 |
5 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/dist/lucky-canvas.cjs.min.js:
--------------------------------------------------------------------------------
1 | "use strict";Object.defineProperty(exports,"__esModule",{value:!0});
2 | /*! *****************************************************************************
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 | ***************************************************************************** */
16 | var t=function(e,i){return(t=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])})(e,i)};function e(e,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=e}t(e,i),e.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var i=function(){return(i=Object.assign||function(t){for(var e,i=1,n=arguments.length;i0&&r[r.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!r||o[1]>r[0]&&o[1]this.length)&&-1!==this.indexOf(t,e)}),Array.prototype.find||Object.defineProperty(Array.prototype,"find",{value:function(t){if(null==this)throw new TypeError('"this" is null or not defined');var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError("predicate must be a function");for(var n=arguments[1],r=0;r '"+e.src+"' 不能为空或不合法"),"WEB"===n.config.flag){var s=new Image;s.src=t,s.onload=function(){return r(s)},s.onerror=function(){return o("=> '"+e.src+"' 图片加载失败")}}else e[i]=r}))},t.prototype.drawImage=function(t,e,i,n,r){var o,s=this.config,a=this.ctx;return["WEB","MP-WX"].includes(s.flag)?o=t:["UNI-H5","UNI-MP","TARO-H5","TARO-MP"].includes(s.flag)&&(o=t.path),a.drawImage(o,e,i,n,r)},t.prototype.getLength=function(t){return s(t,"number")?t:s(t,"string")?this.changeUnits(t):0},t.prototype.changeUnits=function(t,e){var i=this;return void 0===e&&(e=1),Number(t.replace(/^([-]*[0-9.]*)([a-z%]*)$/,(function(t,n,r){var o={"%":function(t){return t*(e/100)},px:function(t){return 1*t},rem:function(t){return t*i.htmlFontSize}}[r];if(o)return o(n);var s=i.config.unitFunc;return s?s(n,r):n})))},t.prototype.$set=function(t,e,i){t&&"object"==typeof t&&m(t,e,i)},t.prototype.$computed=function(t,e,i){var n=this;Object.defineProperty(t,e,{get:function(){return i.call(n)}})},t.prototype.$watch=function(t,e,i){void 0===i&&(i={}),"object"==typeof e&&(e=(i=e).handler);var n=new b(this,t,e,i);return i.immediate&&e.call(this,n.value),function(){}},t}(),w=function(t){return Math.PI/180*t},x=function(t,e){return[+(Math.cos(t)*e).toFixed(8),+(Math.sin(t)*e).toFixed(8)]},z=function(t,e){var i=-t/e;return[i,-i*t+e]},I=function(t,e,i,n,r,o){var s;if(void 0===o&&(o=!0),Math.abs(r-n).toFixed(8)>=w(180).toFixed(8)){var a=(r+n)/2;return o?(I(t,e,i,n,a,o),I(t,e,i,a,r,o)):(I(t,e,i,a,r,o),I(t,e,i,n,a,o)),!1}o||(n=(s=[r,n])[0],r=s[1]);var u=x(n,i),h=u[0],c=u[1],l=x(r,i),f=l[0],d=l[1],p=z(h,c),g=p[0],m=p[1],v=z(f,d),b=v[0],y=v[1],k=(y-m)/(g-b),S=(b*m-g*y)/(b-g);isNaN(k)&&(Math.abs(h)===+i.toFixed(8)&&(k=h),Math.abs(f)===+i.toFixed(8)&&(k=f)),g===1/0||g===-1/0?S=b*k+y:b!==1/0&&b!==-1/0||(S=g*k+m),e.lineTo(h,c),t.indexOf("MP")>0?e.quadraticCurveTo(k,S,f,d):e.arcTo(k,S,f,d,i)},k=function(t,e,i,n,r,o,s,a){i||(i=s);var u=w(90/Math.PI/n*s),h=w(90/Math.PI/i*s),c=r+u,l=o-u,f=r+h,d=o-h;e.beginPath(),e.fillStyle=a,e.moveTo.apply(e,x(c,n)),I(t,e,n,c,l,!0),d>f?I(t,e,i,f,d,!1):e.lineTo.apply(e,x((r+o)/2,s/2/Math.abs(Math.sin((r-o)/2)))),e.closePath(),e.fill()},S=function(t,e,i,n,r,o,s){var a=Math.min(n,r);o>a/2&&(o=a/2),t.beginPath(),t.fillStyle=s,t.moveTo(e+o,i),t.lineTo(e+o,i),t.lineTo(e+n-o,i),t.quadraticCurveTo(e+n,i,e+n,i+o),t.lineTo(e+n,i+r-o),t.quadraticCurveTo(e+n,i+r,e+n-o,i+r),t.lineTo(e+o,i+r),t.quadraticCurveTo(e,i+r,e,i+r-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath(),t.fill()},T={easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*(t/=n)*(t-2)+e}},C={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*Math.cos(t/n*(Math.PI/2))+i+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sin(t/n*(Math.PI/2))+e}},W={easeIn:function(t,e,i,n){return t>=n&&(t=n),0==t?e:i*Math.pow(2,10*(t/n-1))+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),t==n?e+i:i*(1-Math.pow(2,-10*t/n))+e}},O={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*(Math.sqrt(1-(t/=n)*t)-1)+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sqrt(1-(t=t/n-1)*t)+e}},_=Object.freeze({__proto__:null,quad:T,cubic:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t+1)+e}},quart:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*((t=t/n-1)*t*t*t-1)+e}},quint:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t*t*t+1)+e}},sine:C,expo:W,circ:O}),E=function(t){function o(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;return r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},r.defaultStyle={},r._defaultStyle={fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},r.Radius=0,r.prizeRadius=0,r.prizeDeg=0,r.prizeRadian=0,r.rotateDeg=0,r.maxBtnRadius=0,r.startTime=0,r.endTime=0,r.stopDeg=0,r.endDeg=0,r.FPS=16.6,r.blockImgs=[[]],r.prizeImgs=[[]],r.btnImgs=[[]],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:r.buttons.map((function(t){return t.imgs}))}),r}return e(o,t),o.prototype.initData=function(t){this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},o.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){return i({gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},t.defaultConfig)})),this.$computed(this,"_defaultStyle",(function(){return i({fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)}))},o.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){return t.init({btnImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},o.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this.config,s=this.ctx;this.Radius=Math.min(this.boxWidth,this.boxHeight)/2,null===(i=o.beforeInit)||void 0===i||i.call(this),s.translate(this.Radius,this.Radius),this.draw(),this.draw(),Object.keys(e).forEach((function(t){var i=t,n={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i],o=e[i];o&&o.forEach((function(t,e){t&&t.forEach((function(t,o){r.loadAndCacheImg(n,e,i,o,(function(){r.draw()}))}))}))})),null===(n=o.afterInit)||void 0===n||n.call(this)},o.prototype.handleClick=function(t){var e,i=this.ctx;i.beginPath(),i.arc(0,0,this.maxBtnRadius,0,2*Math.PI,!1),i.isPointInPath(t.offsetX,t.offsetY)&&(this.startTime||null===(e=this.startCallback)||void 0===e||e.call(this,t))},o.prototype.loadAndCacheImg=function(t,e,i,o,s){return n(this,void 0,void 0,(function(){var n,a,u=this;return r(this,(function(r){return(n=this[t][e])&&n.imgs&&(a=n.imgs[o])?(this[i][e]||(this[i][e]=[]),this.loadImg(a.src,a).then((function(t){u[i][e][o]=t,s.call(u)})).catch((function(i){console.error(t+"["+e+"].imgs["+o+"] "+i)})),[2]):[2]}))}))},o.prototype.computedWidthAndHeight=function(t,e,i,n){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var r=this.getWidth(e.width,i);return[r,t.height*(r/t.width)]}if(!e.width&&e.height){var o=this.getHeight(e.height,n);return[t.width*(o/t.height),o]}return[this.getWidth(e.width,i),this.getHeight(e.height,n)]},o.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,o=n.ctx,s=n._defaultConfig,h=n._defaultStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,o),o.clearRect(-this.Radius,-this.Radius,2*this.Radius,2*this.Radius),this.prizeRadius=this.blocks.reduce((function(t,e,n){return u(e.background)&&(o.beginPath(),o.fillStyle=e.background,o.arc(0,0,t,0,2*Math.PI,!1),o.fill()),e.imgs&&e.imgs.forEach((function(e,r){if(i.blockImgs[n]){var s=i.blockImgs[n][r];if(s){var a=i.computedWidthAndHeight(s,e,2*t,2*t),u=a[0],h=a[1],c=[i.getOffsetX(u),i.getHeight(e.top,2*t)-t],l=c[0],f=c[1];o.save(),e.rotate&&o.rotate(w(i.rotateDeg)),i.drawImage(s,l,f,u,h),o.restore()}}})),t-i.getLength(e.padding&&e.padding.split(" ")[0])}),this.Radius),this.prizeDeg=360/this.prizes.length,this.prizeRadian=w(this.prizeDeg);var c=w(-90+this.rotateDeg+s.offsetDegree),l=function(t){return i.getOffsetX(o.measureText(t).width)},f=function(t,e,n){var r=t.lineHeight||h.lineHeight||t.fontSize||h.fontSize;return i.getHeight(t.top,e)+(n+1)*i.getLength(r)};o.save(),this.prizes.forEach((function(t,e){var n=c+e*i.prizeRadian,d=i.prizeRadius-i.maxBtnRadius,p=t.background||h.background;u(p)&&function(t,e,i,n,r,o,s,a){s?k(t,e,i,n,r,o,s,a):(e.beginPath(),e.fillStyle=a,e.moveTo(0,0),e.arc(0,0,n,r,o,!1),e.closePath(),e.fill())}(r.flag,o,i.maxBtnRadius,i.prizeRadius,n-i.prizeRadian/2,n+i.prizeRadian/2,i.getLength(s.gutter),p);var g=Math.cos(n)*i.prizeRadius,m=Math.sin(n)*i.prizeRadius;o.translate(g,m),o.rotate(n+w(90)),t.imgs&&t.imgs.forEach((function(t,n){if(i.prizeImgs[e]){var r=i.prizeImgs[e][n];if(r){var o=i.computedWidthAndHeight(r,t,i.prizeRadian*i.prizeRadius,d),s=o[0],a=o[1],u=[i.getOffsetX(s),i.getHeight(t.top,d)],h=u[0],c=u[1];i.drawImage(r,h,c,s,a)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||h.fontColor,n=t.fontWeight||h.fontWeight,r=i.getLength(t.fontSize||h.fontSize),u=t.fontStyle||h.fontStyle;o.fillStyle=e,o.font=n+" "+(r>>0)+"px "+u;var c=[],p=String(t.text);if(Object.prototype.hasOwnProperty.call(t,"wordWrap")?t.wordWrap:h.wordWrap){p=a(p);for(var g="",m=0;m i.getWidth(t.lengthLimit||h.lengthLimit,b)&&(c.push(g.slice(0,-1)),g=p[m])}g&&c.push(g),c.length||c.push(p)}else c=p.split("\n");c.filter((function(t){return!!t})).forEach((function(e,i){o.fillText(e,l(e),f(t,d,i))}))})),o.rotate(w(360)-n-w(90)),o.translate(-g,-m)})),o.restore(),this.buttons.forEach((function(t,e){var n=i.getHeight(t.radius);i.maxBtnRadius=Math.max(i.maxBtnRadius,n),u(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.arc(0,0,n,0,2*Math.PI,!1),o.fill()),t.pointer&&u(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.moveTo(-n,0),o.lineTo(n,0),o.lineTo(0,2*-n),o.closePath(),o.fill()),t.imgs&&t.imgs.forEach((function(t,r){if(i.btnImgs[e]){var o=i.btnImgs[e][r];if(o){var s=i.computedWidthAndHeight(o,t,2*n,2*n),a=s[0],u=s[1],h=[i.getOffsetX(a),i.getHeight(t.top,n)],c=h[0],l=h[1];i.drawImage(o,c,l,a,u)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||h.fontColor,r=t.fontWeight||h.fontWeight,s=i.getLength(t.fontSize||h.fontSize),a=t.fontStyle||h.fontStyle;o.fillStyle=e,o.font=r+" "+(s>>0)+"px "+a,String(t.text).split("\n").forEach((function(e,i){o.fillText(e,l(e),f(t,n,i))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,o)},o.prototype.play=function(){this.startTime||(this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},o.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.rotateDeg=this.prizeDeg/2-this._defaultConfig.offsetDegree,this.draw())},o.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.prizeFlag,r=e.prizeDeg,o=e.rotateDeg,s=e._defaultConfig;if(-1!==n){var a=Date.now()-this.startTime;if(a>=s.accelerationTime&&void 0!==n){this.FPS=a/t,this.endTime=Date.now(),this.stopDeg=o;for(var u=(Math.random()*r-r/2)*this.getLength(s.stopRange),h=0;++h;){var c=360*h-n*r-o-s.offsetDegree+u;if(_[s.speedFunction].easeOut(this.FPS,this.stopDeg,c,s.decelerationTime)-this.stopDeg>s.speed){this.endDeg=c;break}}return this.slowDown()}this.rotateDeg=(o+_[s.speedFunction].easeIn(a,0,s.speed,s.accelerationTime))%360,this.draw(),i(this.run.bind(this,t+1))}else this.startTime=0},o.prototype.slowDown=function(){var t,e=this,n=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopDeg,a=e.endDeg,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>=u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,i({},r.find((function(t,e){return e===o})))));this.rotateDeg=_[u.speedFunction].easeOut(h,s,a,u.decelerationTime)%360,this.draw(),n(this.slowDown.bind(this))}else this.startTime=0},o.prototype.getWidth=function(t,e){return void 0===e&&(e=this.prizeRadian*this.prizeRadius),s(t,"number")?t:s(t,"string")?this.changeUnits(t,e):0},o.prototype.getHeight=function(t,e){return void 0===e&&(e=this.prizeRadius),s(t,"number")?t:s(t,"string")?this.changeUnits(t,e):0},o.prototype.getOffsetX=function(t){return-t/2},o.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr-this.Radius,e/i.dpr-this.Radius]},o}(y),P=function(t){function h(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;r.rows=3,r.cols=3,r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},r.defaultStyle={},r._defaultStyle={borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},r.activeStyle={},r._activeStyle={background:"#ffce98",shadow:"",fontStyle:"",fontWeight:"",fontSize:"",lineHeight:"",fontColor:""},r.cellWidth=0,r.cellHeight=0,r.startTime=0,r.endTime=0,r.currIndex=0,r.stopIndex=0,r.endIndex=0,r.demo=!1,r.timer=0,r.FPS=16.6,r.prizeFlag=-1,r.cells=[],r.blockImgs=[[]],r.btnImgs=[[]],r.prizeImgs=[],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r);var o=r.buttons.map((function(t){return t.imgs}));return r.button&&o.push(r.button.imgs),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:o}),r}return e(h,t),h.prototype.initData=function(t){this.$set(this,"rows",Number(t.rows)||3),this.$set(this,"cols",Number(t.cols)||3),this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"button",t.button),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"activeStyle",t.activeStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},h.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){var e=i({gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},t.defaultConfig);return e.gutter=t.getLength(e.gutter),e.speed=e.speed/40,e})),this.$computed(this,"_defaultStyle",(function(){return i({borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)})),this.$computed(this,"_activeStyle",(function(){return i({background:"#ffce98",shadow:""},t.activeStyle)}))},h.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){var i=e.map((function(t){return t.imgs}));return t.button&&i.push(t.button.imgs),t.init({btnImgs:i})}),{deep:!0}),this.$watch("button",(function(){var e=t.buttons.map((function(t){return t.imgs}));return t.button&&e.push(t.button.imgs),t.init({btnImgs:e})}),{deep:!0}),this.$watch("rows",(function(){return t.init({})})),this.$watch("cols",(function(){return t.init({})})),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("activeStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},h.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this,s=o.config;o.ctx,o.button,null===(i=s.beforeInit)||void 0===i||i.call(this),this.draw(),Object.keys(e).forEach((function(t){var i=t,n=e[i],o={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i];n&&n.forEach((function(t,e){t&&t.forEach((function(t,n){r.loadAndCacheImg(o,e,i,n,(function(){r.draw()}))}))}))})),null===(n=s.afterInit)||void 0===n||n.call(this)},h.prototype.handleClick=function(t){var e=this,i=this.ctx;o(o([],this.buttons),[this.button]).forEach((function(n){var r;if(n){var o=e.getGeometricProperty([n.x,n.y,n.col||1,n.row||1]),s=o[0],a=o[1],u=o[2],h=o[3];i.beginPath(),i.rect(s,a,u,h),i.isPointInPath(t.offsetX,t.offsetY)&&(e.startTime||("function"==typeof n.callback&&n.callback.call(e,n),null===(r=e.startCallback)||void 0===r||r.call(e,t,n)))}}))},h.prototype.loadAndCacheImg=function(t,e,i,o,s){return n(this,void 0,void 0,(function(){var n,a,u,h=this;return r(this,(function(r){return n=this[t][e],"buttons"===t&&!this.buttons.length&&this.button&&(n=this.button),n&&n.imgs&&(a=n.imgs[o])?(this[i][e]||(this[i][e]=[]),u=[this.loadImg(a.src,a),a.activeSrc&&this.loadImg(a.activeSrc,a,"$activeResolve")],Promise.all(u).then((function(t){var n=t[0],r=t[1];h[i][e][o]={defaultImg:n,activeImg:r},s.call(h)})).catch((function(i){console.error(t+"["+e+"].imgs["+o+"] "+i)})),[2]):[2]}))}))},h.prototype.computedWidthAndHeight=function(t,e,i){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var n=this.getWidth(e.width,i.col);return[n,t.height*(n/t.width)]}if(!e.width&&e.height){var r=this.getHeight(e.height,i.row);return[t.width*(r/t.height),r]}return[this.getWidth(e.width,i.col),this.getHeight(e.height,i.row)]},h.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,h=n.ctx,c=n._defaultConfig,l=n._defaultStyle,f=n._activeStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,h),h.clearRect(0,0,this.boxWidth,this.boxHeight),this.cells=o(o([],this.prizes),this.buttons),this.button&&this.cells.push(this.button),this.cells.forEach((function(t){t.col=t.col||1,t.row=t.row||1})),this.prizeArea=this.blocks.reduce((function(t,e){var n=t.x,r=t.y,o=t.w,a=t.h,c=function(t){var e,i=(null===(e=t.padding)||void 0===e?void 0:e.replace(/px/g,"").split(" ").map((function(t){return~~t})))||[0],n=0,r=0,o=0,a=0;switch(i.length){case 1:n=r=o=a=i[0];break;case 2:n=r=i[0],o=a=i[1];break;case 3:n=i[0],o=a=i[1],r=i[2];break;default:n=i[0],r=i[1],o=i[2],a=i[3]}var u={paddingTop:n,paddingBottom:r,paddingLeft:o,paddingRight:a};for(var h in u)u[h]=Object.prototype.hasOwnProperty.call(t,h)&&s(t[h],"string","number")?~~String(t[h]).replace(/px/g,""):u[h];return[n,r,o,a]}(e),f=c[0],d=c[1],p=c[2],g=c[3],m=e.borderRadius?i.getLength(e.borderRadius):0,v=e.background||l.background;return u(v)&&S(h,n,r,o,a,m,i.handleBackground(n,r,o,a,v)),{x:n+p,y:r+f,w:o-p-g,h:a-f-d}}),{x:0,y:0,w:this.boxWidth,h:this.boxHeight}),this.cellWidth=(this.prizeArea.w-c.gutter*(this.cols-1))/this.cols,this.cellHeight=(this.prizeArea.h-c.gutter*(this.rows-1))/this.rows,this.cells.forEach((function(t,e){var n=i.getGeometricProperty([t.x,t.y,t.col,t.row]),o=n[0],s=n[1],c=n[2],d=n[3],p=!1;(void 0===i.prizeFlag||i.prizeFlag>-1)&&(p=e===i.currIndex%i.prizes.length>>0);var g=p?f.background:t.background||l.background;if(u(g)){var m=(p?f.shadow:t.shadow||l.shadow).replace(/px/g,"").split(",")[0].split(" ").map((function(t,e){return e<3?Number(t):t}));4===m.length&&(h.shadowColor=m[3],h.shadowOffsetX=m[0]*r.dpr,h.shadowOffsetY=m[1]*r.dpr,h.shadowBlur=m[2],m[0]>0?c-=m[0]:(c+=m[0],o-=m[0]),m[1]>0?d-=m[1]:(d+=m[1],s-=m[1])),S(h,o,s,c,d,i.getLength(t.borderRadius?t.borderRadius:l.borderRadius),i.handleBackground(o,s,c,d,g)),h.shadowColor="rgba(0, 0, 0, 0)",h.shadowOffsetX=0,h.shadowOffsetY=0,h.shadowBlur=0}var v="prizeImgs";e>=i.prizes.length&&(v="btnImgs",e-=i.prizes.length),t.imgs&&t.imgs.forEach((function(n,r){if(i[v][e]){var a=i[v][e][r];if(a){var u=p&&a.activeImg||a.defaultImg;if(u){var h=i.computedWidthAndHeight(u,n,t),c=h[0],l=h[1],f=[o+i.getOffsetX(c,t.col),s+i.getHeight(n.top,t.row)],d=f[0],g=f[1];i.drawImage(u,d,g,c,l)}}}})),t.fonts&&t.fonts.forEach((function(e){var n=p&&f.fontStyle?f.fontStyle:e.fontStyle||l.fontStyle,r=p&&f.fontWeight?f.fontWeight:e.fontWeight||l.fontWeight,u=p&&f.fontSize?i.getLength(f.fontSize):i.getLength(e.fontSize||l.fontSize),c=p&&f.lineHeight?f.lineHeight:e.lineHeight||l.lineHeight||e.fontSize||l.fontSize;h.font=r+" "+(u>>0)+"px "+n,h.fillStyle=p&&f.fontColor?f.fontColor:e.fontColor||l.fontColor;var d=[],g=String(e.text);if(Object.prototype.hasOwnProperty.call(e,"wordWrap")?e.wordWrap:l.wordWrap){g=a(g);for(var m="",v=0;vi.getWidth(e.lengthLimit||l.lengthLimit,t.col)&&(d.push(m.slice(0,-1)),m=g[v])}m&&d.push(m),d.length||d.push(g)}else d=g.split("\n");d.forEach((function(n,r){h.fillText(n,o+i.getOffsetX(h.measureText(n).width,t.col),s+i.getHeight(e.top,t.row)+(r+1)*i.getLength(c))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,h)},h.prototype.handleBackground=function(t,e,i,n,r){var o=this.ctx;return r.includes("linear-gradient")&&(r=function(t,e,i,n,r,o){var s=/linear-gradient\((.+)\)/.exec(o)[1].split(",").map((function(t){return t.trim()})),a=s.shift(),u=[0,0,0,0];if(a.includes("deg")){var h=function(t){return Math.tan(t/180*Math.PI)};(a=a.slice(0,-3)%360)>=0&&a<45?u=[e,i+r,e+n,i+r-n*h(a-0)]:a>=45&&a<90?u=[e,i+r,e+n-r*h(a-45),i]:a>=90&&a<135?u=[e+n,i+r,e+n-r*h(a-90),i]:a>=135&&a<180?u=[e+n,i+r,e,i+n*h(a-135)]:a>=180&&a<225?u=[e+n,i,e,i+n*h(a-180)]:a>=225&&a<270?u=[e+n,i,e+r*h(a-225),i+r]:a>=270&&a<315?u=[e,i,e+r*h(a-270),i+r]:a>=315&&a<360&&(u=[e,i,e+n,i+r-n*h(a-315)])}else a.includes("top")?u=[e,i+r,e,i]:a.includes("bottom")?u=[e,i,e,i+r]:a.includes("left")?u=[e+n,i,e,i]:a.includes("right")&&(u=[e,i,e+n,i]);var c=t.createLinearGradient.apply(t,u.map((function(t){return t>>0})));return s.reduce((function(t,e,i){var n=e.split(" ");return 1===n.length?t.addColorStop(i,n[0]):2===n.length&&t.addColorStop.apply(t,n),t}),c)}(o,t,e,i,n,r)),r},h.prototype.play=function(){var t=this.config.clearInterval;this.startTime||(t(this.timer),this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},h.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.currIndex=0,this.draw())},h.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.currIndex,r=e.prizes,o=e.prizeFlag,s=e.startTime,a=e._defaultConfig,u=Date.now()-s;if(u>=a.accelerationTime&&void 0!==o){this.FPS=u/t,this.endTime=Date.now(),this.stopIndex=n;for(var h=0;++h;){var c=r.length*h+o-(n>>0);if(T.easeOut(this.FPS,this.stopIndex,c,a.decelerationTime)-this.stopIndex>a.speed){this.endIndex=c;break}}return this.slowDown()}this.currIndex=(n+T.easeIn(u,.1,a.speed,a.accelerationTime))%r.length,this.draw(),i(this.run.bind(this,t+1))},h.prototype.slowDown=function(){var t,e=this,n=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopIndex,a=e.endIndex,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,i({},r.find((function(t,e){return e===o})))));this.currIndex=T.easeOut(h,s,a,u.decelerationTime)%r.length,this.draw(),n(this.slowDown.bind(this))}else this.startTime=0},h.prototype.walk=function(){var t=this,e=this.config,i=e.setInterval;(0,e.clearInterval)(this.timer),this.timer=i((function(){t.currIndex+=1,t.draw()}),1300)},h.prototype.getGeometricProperty=function(t){var e=t[0],i=t[1],n=t[2],r=t[3],o=this.cellWidth,s=this.cellHeight,a=this._defaultConfig.gutter,u=[this.prizeArea.x+(o+a)*e,this.prizeArea.y+(s+a)*i];return n&&r&&u.push(o*n+a*(n-1),s*r+a*(r-1)),u},h.prototype.getWidth=function(t,e){return void 0===e&&(e=1),s(t,"number")?t:s(t,"string")?this.changeUnits(t,this.cellWidth*e+this._defaultConfig.gutter*(e-1)):0},h.prototype.getHeight=function(t,e){return void 0===e&&(e=1),s(t,"number")?t:s(t,"string")?this.changeUnits(t,this.cellHeight*e+this._defaultConfig.gutter*(e-1)):0},h.prototype.getOffsetX=function(t,e){return void 0===e&&(e=1),(this.cellWidth*e+this._defaultConfig.gutter*(e-1)-t)/2},h.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr,e/i.dpr]},h}(y);exports.LuckyGrid=P,exports.LuckyWheel=E;
17 |
--------------------------------------------------------------------------------
/dist/lucky-canvas.umd.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).LuckyCanvas={})}(this,(function(t){"use strict";
2 | /*! *****************************************************************************
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 | ***************************************************************************** */var e=function(t,i){return(e=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])})(t,i)};function i(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var n=function(){return(n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i0&&r[r.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!r||o[1]>r[0]&&o[1]this.length)&&-1!==this.indexOf(t,e)}),Array.prototype.find||Object.defineProperty(Array.prototype,"find",{value:function(t){if(null==this)throw new TypeError('"this" is null or not defined');var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError("predicate must be a function");for(var n=arguments[1],r=0;r '"+e.src+"' 不能为空或不合法"),"WEB"===n.config.flag){var s=new Image;s.src=t,s.onload=function(){return r(s)},s.onerror=function(){return o("=> '"+e.src+"' 图片加载失败")}}else e[i]=r}))},t.prototype.drawImage=function(t,e,i,n,r){var o,s=this.config,a=this.ctx;return["WEB","MP-WX"].includes(s.flag)?o=t:["UNI-H5","UNI-MP","TARO-H5","TARO-MP"].includes(s.flag)&&(o=t.path),a.drawImage(o,e,i,n,r)},t.prototype.getLength=function(t){return a(t,"number")?t:a(t,"string")?this.changeUnits(t):0},t.prototype.changeUnits=function(t,e){var i=this;return void 0===e&&(e=1),Number(t.replace(/^([-]*[0-9.]*)([a-z%]*)$/,(function(t,n,r){var o={"%":function(t){return t*(e/100)},px:function(t){return 1*t},rem:function(t){return t*i.htmlFontSize}}[r];if(o)return o(n);var s=i.config.unitFunc;return s?s(n,r):n})))},t.prototype.$set=function(t,e,i){t&&"object"==typeof t&&v(t,e,i)},t.prototype.$computed=function(t,e,i){var n=this;Object.defineProperty(t,e,{get:function(){return i.call(n)}})},t.prototype.$watch=function(t,e,i){void 0===i&&(i={}),"object"==typeof e&&(e=(i=e).handler);var n=new y(this,t,e,i);return i.immediate&&e.call(this,n.value),function(){}},t}(),x=function(t){return Math.PI/180*t},z=function(t,e){return[+(Math.cos(t)*e).toFixed(8),+(Math.sin(t)*e).toFixed(8)]},I=function(t,e){var i=-t/e;return[i,-i*t+e]},k=function(t,e,i,n,r,o){var s;if(void 0===o&&(o=!0),Math.abs(r-n).toFixed(8)>=x(180).toFixed(8)){var a=(r+n)/2;return o?(k(t,e,i,n,a,o),k(t,e,i,a,r,o)):(k(t,e,i,a,r,o),k(t,e,i,n,a,o)),!1}o||(n=(s=[r,n])[0],r=s[1]);var u=z(n,i),h=u[0],c=u[1],l=z(r,i),f=l[0],d=l[1],p=I(h,c),g=p[0],m=p[1],v=I(f,d),b=v[0],y=v[1],w=(y-m)/(g-b),S=(b*m-g*y)/(b-g);isNaN(w)&&(Math.abs(h)===+i.toFixed(8)&&(w=h),Math.abs(f)===+i.toFixed(8)&&(w=f)),g===1/0||g===-1/0?S=b*w+y:b!==1/0&&b!==-1/0||(S=g*w+m),e.lineTo(h,c),t.indexOf("MP")>0?e.quadraticCurveTo(w,S,f,d):e.arcTo(w,S,f,d,i)},S=function(t,e,i,n,r,o,s,a){i||(i=s);var u=x(90/Math.PI/n*s),h=x(90/Math.PI/i*s),c=r+u,l=o-u,f=r+h,d=o-h;e.beginPath(),e.fillStyle=a,e.moveTo.apply(e,z(c,n)),k(t,e,n,c,l,!0),d>f?k(t,e,i,f,d,!1):e.lineTo.apply(e,z((r+o)/2,s/2/Math.abs(Math.sin((r-o)/2)))),e.closePath(),e.fill()},T=function(t,e,i,n,r,o,s){var a=Math.min(n,r);o>a/2&&(o=a/2),t.beginPath(),t.fillStyle=s,t.moveTo(e+o,i),t.lineTo(e+o,i),t.lineTo(e+n-o,i),t.quadraticCurveTo(e+n,i,e+n,i+o),t.lineTo(e+n,i+r-o),t.quadraticCurveTo(e+n,i+r,e+n-o,i+r),t.lineTo(e+o,i+r),t.quadraticCurveTo(e,i+r,e,i+r-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath(),t.fill()},C={easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*(t/=n)*(t-2)+e}},W={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*Math.cos(t/n*(Math.PI/2))+i+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sin(t/n*(Math.PI/2))+e}},O={easeIn:function(t,e,i,n){return t>=n&&(t=n),0==t?e:i*Math.pow(2,10*(t/n-1))+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),t==n?e+i:i*(1-Math.pow(2,-10*t/n))+e}},_={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*(Math.sqrt(1-(t/=n)*t)-1)+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sqrt(1-(t=t/n-1)*t)+e}},E=Object.freeze({__proto__:null,quad:C,cubic:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t+1)+e}},quart:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*((t=t/n-1)*t*t*t-1)+e}},quint:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t*t*t+1)+e}},sine:W,expo:O,circ:_}),P=function(t){function e(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;return r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},r.defaultStyle={},r._defaultStyle={fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},r.Radius=0,r.prizeRadius=0,r.prizeDeg=0,r.prizeRadian=0,r.rotateDeg=0,r.maxBtnRadius=0,r.startTime=0,r.endTime=0,r.stopDeg=0,r.endDeg=0,r.FPS=16.6,r.blockImgs=[[]],r.prizeImgs=[[]],r.btnImgs=[[]],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:r.buttons.map((function(t){return t.imgs}))}),r}return i(e,t),e.prototype.initData=function(t){this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},e.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){return n({gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},t.defaultConfig)})),this.$computed(this,"_defaultStyle",(function(){return n({fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)}))},e.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){return t.init({btnImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},e.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this.config,s=this.ctx;this.Radius=Math.min(this.boxWidth,this.boxHeight)/2,null===(i=o.beforeInit)||void 0===i||i.call(this),s.translate(this.Radius,this.Radius),this.draw(),this.draw(),Object.keys(e).forEach((function(t){var i=t,n={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i],o=e[i];o&&o.forEach((function(t,e){t&&t.forEach((function(t,o){r.loadAndCacheImg(n,e,i,o,(function(){r.draw()}))}))}))})),null===(n=o.afterInit)||void 0===n||n.call(this)},e.prototype.handleClick=function(t){var e,i=this.ctx;i.beginPath(),i.arc(0,0,this.maxBtnRadius,0,2*Math.PI,!1),i.isPointInPath(t.offsetX,t.offsetY)&&(this.startTime||null===(e=this.startCallback)||void 0===e||e.call(this,t))},e.prototype.loadAndCacheImg=function(t,e,i,n,s){return r(this,void 0,void 0,(function(){var r,a,u=this;return o(this,(function(o){return(r=this[t][e])&&r.imgs&&(a=r.imgs[n])?(this[i][e]||(this[i][e]=[]),this.loadImg(a.src,a).then((function(t){u[i][e][n]=t,s.call(u)})).catch((function(i){console.error(t+"["+e+"].imgs["+n+"] "+i)})),[2]):[2]}))}))},e.prototype.computedWidthAndHeight=function(t,e,i,n){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var r=this.getWidth(e.width,i);return[r,t.height*(r/t.width)]}if(!e.width&&e.height){var o=this.getHeight(e.height,n);return[t.width*(o/t.height),o]}return[this.getWidth(e.width,i),this.getHeight(e.height,n)]},e.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,o=n.ctx,s=n._defaultConfig,a=n._defaultStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,o),o.clearRect(-this.Radius,-this.Radius,2*this.Radius,2*this.Radius),this.prizeRadius=this.blocks.reduce((function(t,e,n){return h(e.background)&&(o.beginPath(),o.fillStyle=e.background,o.arc(0,0,t,0,2*Math.PI,!1),o.fill()),e.imgs&&e.imgs.forEach((function(e,r){if(i.blockImgs[n]){var s=i.blockImgs[n][r];if(s){var a=i.computedWidthAndHeight(s,e,2*t,2*t),u=a[0],h=a[1],c=[i.getOffsetX(u),i.getHeight(e.top,2*t)-t],l=c[0],f=c[1];o.save(),e.rotate&&o.rotate(x(i.rotateDeg)),i.drawImage(s,l,f,u,h),o.restore()}}})),t-i.getLength(e.padding&&e.padding.split(" ")[0])}),this.Radius),this.prizeDeg=360/this.prizes.length,this.prizeRadian=x(this.prizeDeg);var c=x(-90+this.rotateDeg+s.offsetDegree),l=function(t){return i.getOffsetX(o.measureText(t).width)},f=function(t,e,n){var r=t.lineHeight||a.lineHeight||t.fontSize||a.fontSize;return i.getHeight(t.top,e)+(n+1)*i.getLength(r)};o.save(),this.prizes.forEach((function(t,e){var n=c+e*i.prizeRadian,d=i.prizeRadius-i.maxBtnRadius,p=t.background||a.background;h(p)&&function(t,e,i,n,r,o,s,a){s?S(t,e,i,n,r,o,s,a):(e.beginPath(),e.fillStyle=a,e.moveTo(0,0),e.arc(0,0,n,r,o,!1),e.closePath(),e.fill())}(r.flag,o,i.maxBtnRadius,i.prizeRadius,n-i.prizeRadian/2,n+i.prizeRadian/2,i.getLength(s.gutter),p);var g=Math.cos(n)*i.prizeRadius,m=Math.sin(n)*i.prizeRadius;o.translate(g,m),o.rotate(n+x(90)),t.imgs&&t.imgs.forEach((function(t,n){if(i.prizeImgs[e]){var r=i.prizeImgs[e][n];if(r){var o=i.computedWidthAndHeight(r,t,i.prizeRadian*i.prizeRadius,d),s=o[0],a=o[1],u=[i.getOffsetX(s),i.getHeight(t.top,d)],h=u[0],c=u[1];i.drawImage(r,h,c,s,a)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||a.fontColor,n=t.fontWeight||a.fontWeight,r=i.getLength(t.fontSize||a.fontSize),h=t.fontStyle||a.fontStyle;o.fillStyle=e,o.font=n+" "+(r>>0)+"px "+h;var c=[],p=String(t.text);if(Object.prototype.hasOwnProperty.call(t,"wordWrap")?t.wordWrap:a.wordWrap){p=u(p);for(var g="",m=0;m i.getWidth(t.lengthLimit||a.lengthLimit,b)&&(c.push(g.slice(0,-1)),g=p[m])}g&&c.push(g),c.length||c.push(p)}else c=p.split("\n");c.filter((function(t){return!!t})).forEach((function(e,i){o.fillText(e,l(e),f(t,d,i))}))})),o.rotate(x(360)-n-x(90)),o.translate(-g,-m)})),o.restore(),this.buttons.forEach((function(t,e){var n=i.getHeight(t.radius);i.maxBtnRadius=Math.max(i.maxBtnRadius,n),h(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.arc(0,0,n,0,2*Math.PI,!1),o.fill()),t.pointer&&h(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.moveTo(-n,0),o.lineTo(n,0),o.lineTo(0,2*-n),o.closePath(),o.fill()),t.imgs&&t.imgs.forEach((function(t,r){if(i.btnImgs[e]){var o=i.btnImgs[e][r];if(o){var s=i.computedWidthAndHeight(o,t,2*n,2*n),a=s[0],u=s[1],h=[i.getOffsetX(a),i.getHeight(t.top,n)],c=h[0],l=h[1];i.drawImage(o,c,l,a,u)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||a.fontColor,r=t.fontWeight||a.fontWeight,s=i.getLength(t.fontSize||a.fontSize),u=t.fontStyle||a.fontStyle;o.fillStyle=e,o.font=r+" "+(s>>0)+"px "+u,String(t.text).split("\n").forEach((function(e,i){o.fillText(e,l(e),f(t,n,i))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,o)},e.prototype.play=function(){this.startTime||(this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},e.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.rotateDeg=this.prizeDeg/2-this._defaultConfig.offsetDegree,this.draw())},e.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.prizeFlag,r=e.prizeDeg,o=e.rotateDeg,s=e._defaultConfig;if(-1!==n){var a=Date.now()-this.startTime;if(a>=s.accelerationTime&&void 0!==n){this.FPS=a/t,this.endTime=Date.now(),this.stopDeg=o;for(var u=(Math.random()*r-r/2)*this.getLength(s.stopRange),h=0;++h;){var c=360*h-n*r-o-s.offsetDegree+u;if(E[s.speedFunction].easeOut(this.FPS,this.stopDeg,c,s.decelerationTime)-this.stopDeg>s.speed){this.endDeg=c;break}}return this.slowDown()}this.rotateDeg=(o+E[s.speedFunction].easeIn(a,0,s.speed,s.accelerationTime))%360,this.draw(),i(this.run.bind(this,t+1))}else this.startTime=0},e.prototype.slowDown=function(){var t,e=this,i=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopDeg,a=e.endDeg,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>=u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,n({},r.find((function(t,e){return e===o})))));this.rotateDeg=E[u.speedFunction].easeOut(h,s,a,u.decelerationTime)%360,this.draw(),i(this.slowDown.bind(this))}else this.startTime=0},e.prototype.getWidth=function(t,e){return void 0===e&&(e=this.prizeRadian*this.prizeRadius),a(t,"number")?t:a(t,"string")?this.changeUnits(t,e):0},e.prototype.getHeight=function(t,e){return void 0===e&&(e=this.prizeRadius),a(t,"number")?t:a(t,"string")?this.changeUnits(t,e):0},e.prototype.getOffsetX=function(t){return-t/2},e.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr-this.Radius,e/i.dpr-this.Radius]},e}(w),D=function(t){function e(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;r.rows=3,r.cols=3,r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},r.defaultStyle={},r._defaultStyle={borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},r.activeStyle={},r._activeStyle={background:"#ffce98",shadow:"",fontStyle:"",fontWeight:"",fontSize:"",lineHeight:"",fontColor:""},r.cellWidth=0,r.cellHeight=0,r.startTime=0,r.endTime=0,r.currIndex=0,r.stopIndex=0,r.endIndex=0,r.demo=!1,r.timer=0,r.FPS=16.6,r.prizeFlag=-1,r.cells=[],r.blockImgs=[[]],r.btnImgs=[[]],r.prizeImgs=[],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r);var o=r.buttons.map((function(t){return t.imgs}));return r.button&&o.push(r.button.imgs),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:o}),r}return i(e,t),e.prototype.initData=function(t){this.$set(this,"rows",Number(t.rows)||3),this.$set(this,"cols",Number(t.cols)||3),this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"button",t.button),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"activeStyle",t.activeStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},e.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){var e=n({gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},t.defaultConfig);return e.gutter=t.getLength(e.gutter),e.speed=e.speed/40,e})),this.$computed(this,"_defaultStyle",(function(){return n({borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)})),this.$computed(this,"_activeStyle",(function(){return n({background:"#ffce98",shadow:""},t.activeStyle)}))},e.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){var i=e.map((function(t){return t.imgs}));return t.button&&i.push(t.button.imgs),t.init({btnImgs:i})}),{deep:!0}),this.$watch("button",(function(){var e=t.buttons.map((function(t){return t.imgs}));return t.button&&e.push(t.button.imgs),t.init({btnImgs:e})}),{deep:!0}),this.$watch("rows",(function(){return t.init({})})),this.$watch("cols",(function(){return t.init({})})),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("activeStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},e.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this,s=o.config;o.ctx,o.button,null===(i=s.beforeInit)||void 0===i||i.call(this),this.draw(),Object.keys(e).forEach((function(t){var i=t,n=e[i],o={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i];n&&n.forEach((function(t,e){t&&t.forEach((function(t,n){r.loadAndCacheImg(o,e,i,n,(function(){r.draw()}))}))}))})),null===(n=s.afterInit)||void 0===n||n.call(this)},e.prototype.handleClick=function(t){var e=this,i=this.ctx;s(s([],this.buttons),[this.button]).forEach((function(n){var r;if(n){var o=e.getGeometricProperty([n.x,n.y,n.col||1,n.row||1]),s=o[0],a=o[1],u=o[2],h=o[3];i.beginPath(),i.rect(s,a,u,h),i.isPointInPath(t.offsetX,t.offsetY)&&(e.startTime||("function"==typeof n.callback&&n.callback.call(e,n),null===(r=e.startCallback)||void 0===r||r.call(e,t,n)))}}))},e.prototype.loadAndCacheImg=function(t,e,i,n,s){return r(this,void 0,void 0,(function(){var r,a,u,h=this;return o(this,(function(o){return r=this[t][e],"buttons"===t&&!this.buttons.length&&this.button&&(r=this.button),r&&r.imgs&&(a=r.imgs[n])?(this[i][e]||(this[i][e]=[]),u=[this.loadImg(a.src,a),a.activeSrc&&this.loadImg(a.activeSrc,a,"$activeResolve")],Promise.all(u).then((function(t){var r=t[0],o=t[1];h[i][e][n]={defaultImg:r,activeImg:o},s.call(h)})).catch((function(i){console.error(t+"["+e+"].imgs["+n+"] "+i)})),[2]):[2]}))}))},e.prototype.computedWidthAndHeight=function(t,e,i){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var n=this.getWidth(e.width,i.col);return[n,t.height*(n/t.width)]}if(!e.width&&e.height){var r=this.getHeight(e.height,i.row);return[t.width*(r/t.height),r]}return[this.getWidth(e.width,i.col),this.getHeight(e.height,i.row)]},e.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,o=n.ctx,c=n._defaultConfig,l=n._defaultStyle,f=n._activeStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,o),o.clearRect(0,0,this.boxWidth,this.boxHeight),this.cells=s(s([],this.prizes),this.buttons),this.button&&this.cells.push(this.button),this.cells.forEach((function(t){t.col=t.col||1,t.row=t.row||1})),this.prizeArea=this.blocks.reduce((function(t,e){var n=t.x,r=t.y,s=t.w,u=t.h,c=function(t){var e,i=(null===(e=t.padding)||void 0===e?void 0:e.replace(/px/g,"").split(" ").map((function(t){return~~t})))||[0],n=0,r=0,o=0,s=0;switch(i.length){case 1:n=r=o=s=i[0];break;case 2:n=r=i[0],o=s=i[1];break;case 3:n=i[0],o=s=i[1],r=i[2];break;default:n=i[0],r=i[1],o=i[2],s=i[3]}var u={paddingTop:n,paddingBottom:r,paddingLeft:o,paddingRight:s};for(var h in u)u[h]=Object.prototype.hasOwnProperty.call(t,h)&&a(t[h],"string","number")?~~String(t[h]).replace(/px/g,""):u[h];return[n,r,o,s]}(e),f=c[0],d=c[1],p=c[2],g=c[3],m=e.borderRadius?i.getLength(e.borderRadius):0,v=e.background||l.background;return h(v)&&T(o,n,r,s,u,m,i.handleBackground(n,r,s,u,v)),{x:n+p,y:r+f,w:s-p-g,h:u-f-d}}),{x:0,y:0,w:this.boxWidth,h:this.boxHeight}),this.cellWidth=(this.prizeArea.w-c.gutter*(this.cols-1))/this.cols,this.cellHeight=(this.prizeArea.h-c.gutter*(this.rows-1))/this.rows,this.cells.forEach((function(t,e){var n=i.getGeometricProperty([t.x,t.y,t.col,t.row]),s=n[0],a=n[1],c=n[2],d=n[3],p=!1;(void 0===i.prizeFlag||i.prizeFlag>-1)&&(p=e===i.currIndex%i.prizes.length>>0);var g=p?f.background:t.background||l.background;if(h(g)){var m=(p?f.shadow:t.shadow||l.shadow).replace(/px/g,"").split(",")[0].split(" ").map((function(t,e){return e<3?Number(t):t}));4===m.length&&(o.shadowColor=m[3],o.shadowOffsetX=m[0]*r.dpr,o.shadowOffsetY=m[1]*r.dpr,o.shadowBlur=m[2],m[0]>0?c-=m[0]:(c+=m[0],s-=m[0]),m[1]>0?d-=m[1]:(d+=m[1],a-=m[1])),T(o,s,a,c,d,i.getLength(t.borderRadius?t.borderRadius:l.borderRadius),i.handleBackground(s,a,c,d,g)),o.shadowColor="rgba(0, 0, 0, 0)",o.shadowOffsetX=0,o.shadowOffsetY=0,o.shadowBlur=0}var v="prizeImgs";e>=i.prizes.length&&(v="btnImgs",e-=i.prizes.length),t.imgs&&t.imgs.forEach((function(n,r){if(i[v][e]){var o=i[v][e][r];if(o){var u=p&&o.activeImg||o.defaultImg;if(u){var h=i.computedWidthAndHeight(u,n,t),c=h[0],l=h[1],f=[s+i.getOffsetX(c,t.col),a+i.getHeight(n.top,t.row)],d=f[0],g=f[1];i.drawImage(u,d,g,c,l)}}}})),t.fonts&&t.fonts.forEach((function(e){var n=p&&f.fontStyle?f.fontStyle:e.fontStyle||l.fontStyle,r=p&&f.fontWeight?f.fontWeight:e.fontWeight||l.fontWeight,h=p&&f.fontSize?i.getLength(f.fontSize):i.getLength(e.fontSize||l.fontSize),c=p&&f.lineHeight?f.lineHeight:e.lineHeight||l.lineHeight||e.fontSize||l.fontSize;o.font=r+" "+(h>>0)+"px "+n,o.fillStyle=p&&f.fontColor?f.fontColor:e.fontColor||l.fontColor;var d=[],g=String(e.text);if(Object.prototype.hasOwnProperty.call(e,"wordWrap")?e.wordWrap:l.wordWrap){g=u(g);for(var m="",v=0;vi.getWidth(e.lengthLimit||l.lengthLimit,t.col)&&(d.push(m.slice(0,-1)),m=g[v])}m&&d.push(m),d.length||d.push(g)}else d=g.split("\n");d.forEach((function(n,r){o.fillText(n,s+i.getOffsetX(o.measureText(n).width,t.col),a+i.getHeight(e.top,t.row)+(r+1)*i.getLength(c))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,o)},e.prototype.handleBackground=function(t,e,i,n,r){var o=this.ctx;return r.includes("linear-gradient")&&(r=function(t,e,i,n,r,o){var s=/linear-gradient\((.+)\)/.exec(o)[1].split(",").map((function(t){return t.trim()})),a=s.shift(),u=[0,0,0,0];if(a.includes("deg")){var h=function(t){return Math.tan(t/180*Math.PI)};(a=a.slice(0,-3)%360)>=0&&a<45?u=[e,i+r,e+n,i+r-n*h(a-0)]:a>=45&&a<90?u=[e,i+r,e+n-r*h(a-45),i]:a>=90&&a<135?u=[e+n,i+r,e+n-r*h(a-90),i]:a>=135&&a<180?u=[e+n,i+r,e,i+n*h(a-135)]:a>=180&&a<225?u=[e+n,i,e,i+n*h(a-180)]:a>=225&&a<270?u=[e+n,i,e+r*h(a-225),i+r]:a>=270&&a<315?u=[e,i,e+r*h(a-270),i+r]:a>=315&&a<360&&(u=[e,i,e+n,i+r-n*h(a-315)])}else a.includes("top")?u=[e,i+r,e,i]:a.includes("bottom")?u=[e,i,e,i+r]:a.includes("left")?u=[e+n,i,e,i]:a.includes("right")&&(u=[e,i,e+n,i]);var c=t.createLinearGradient.apply(t,u.map((function(t){return t>>0})));return s.reduce((function(t,e,i){var n=e.split(" ");return 1===n.length?t.addColorStop(i,n[0]):2===n.length&&t.addColorStop.apply(t,n),t}),c)}(o,t,e,i,n,r)),r},e.prototype.play=function(){var t=this.config.clearInterval;this.startTime||(t(this.timer),this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},e.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.currIndex=0,this.draw())},e.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.currIndex,r=e.prizes,o=e.prizeFlag,s=e.startTime,a=e._defaultConfig,u=Date.now()-s;if(u>=a.accelerationTime&&void 0!==o){this.FPS=u/t,this.endTime=Date.now(),this.stopIndex=n;for(var h=0;++h;){var c=r.length*h+o-(n>>0);if(C.easeOut(this.FPS,this.stopIndex,c,a.decelerationTime)-this.stopIndex>a.speed){this.endIndex=c;break}}return this.slowDown()}this.currIndex=(n+C.easeIn(u,.1,a.speed,a.accelerationTime))%r.length,this.draw(),i(this.run.bind(this,t+1))},e.prototype.slowDown=function(){var t,e=this,i=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopIndex,a=e.endIndex,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,n({},r.find((function(t,e){return e===o})))));this.currIndex=C.easeOut(h,s,a,u.decelerationTime)%r.length,this.draw(),i(this.slowDown.bind(this))}else this.startTime=0},e.prototype.walk=function(){var t=this,e=this.config,i=e.setInterval;(0,e.clearInterval)(this.timer),this.timer=i((function(){t.currIndex+=1,t.draw()}),1300)},e.prototype.getGeometricProperty=function(t){var e=t[0],i=t[1],n=t[2],r=t[3],o=this.cellWidth,s=this.cellHeight,a=this._defaultConfig.gutter,u=[this.prizeArea.x+(o+a)*e,this.prizeArea.y+(s+a)*i];return n&&r&&u.push(o*n+a*(n-1),s*r+a*(r-1)),u},e.prototype.getWidth=function(t,e){return void 0===e&&(e=1),a(t,"number")?t:a(t,"string")?this.changeUnits(t,this.cellWidth*e+this._defaultConfig.gutter*(e-1)):0},e.prototype.getHeight=function(t,e){return void 0===e&&(e=1),a(t,"number")?t:a(t,"string")?this.changeUnits(t,this.cellHeight*e+this._defaultConfig.gutter*(e-1)):0},e.prototype.getOffsetX=function(t,e){return void 0===e&&(e=1),(this.cellWidth*e+this._defaultConfig.gutter*(e-1)-t)/2},e.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr,e/i.dpr]},e}(w);t.LuckyGrid=D,t.LuckyWheel=P,Object.defineProperty(t,"__esModule",{value:!0})}));
16 |
--------------------------------------------------------------------------------
/examples/ggk/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | Document
8 |
9 |
10 |
11 |
12 | 开始
13 | 重置
14 | 重置
15 |
16 |
17 |
41 |
42 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 | 官网文档
10 |
18 | 大转盘示例
19 |
22 | 九宫格示例
23 |
26 | 刮刮乐示例
27 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/examples/ymc/img/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/0.png
--------------------------------------------------------------------------------
/examples/ymc/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/1.png
--------------------------------------------------------------------------------
/examples/ymc/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/2.png
--------------------------------------------------------------------------------
/examples/ymc/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/3.png
--------------------------------------------------------------------------------
/examples/ymc/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/4.png
--------------------------------------------------------------------------------
/examples/ymc/img/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/5.png
--------------------------------------------------------------------------------
/examples/ymc/img/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/6.png
--------------------------------------------------------------------------------
/examples/ymc/img/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/7.png
--------------------------------------------------------------------------------
/examples/ymc/img/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/ymc/img/button.png
--------------------------------------------------------------------------------
/examples/ymc/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/examples/yyjk/img/0.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/0.png
--------------------------------------------------------------------------------
/examples/yyjk/img/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/1.png
--------------------------------------------------------------------------------
/examples/yyjk/img/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/2.png
--------------------------------------------------------------------------------
/examples/yyjk/img/3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/3.png
--------------------------------------------------------------------------------
/examples/yyjk/img/4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/4.png
--------------------------------------------------------------------------------
/examples/yyjk/img/5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/5.png
--------------------------------------------------------------------------------
/examples/yyjk/img/6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/6.png
--------------------------------------------------------------------------------
/examples/yyjk/img/7.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/7.png
--------------------------------------------------------------------------------
/examples/yyjk/img/btn.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/btn.png
--------------------------------------------------------------------------------
/examples/yyjk/img/button.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/examples/yyjk/img/button.png
--------------------------------------------------------------------------------
/examples/yyjk/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Document
7 |
8 |
9 |
10 |
11 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | module.exports = require('./dist/lucky-canvas.cjs.min.js')
2 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/logo.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lucky-canvas",
3 | "version": "1.5.4",
4 | "description": "一个基于原生 js 的(大转盘抽奖 / 九宫格抽奖)插件",
5 | "main": "dist/lucky-canvas.cjs.js",
6 | "scripts": {
7 | "dev": "rollup --config rollup.config.dev.js -w",
8 | "build": "rollup --config rollup.config.build.js"
9 | },
10 | "repository": {
11 | "type": "git",
12 | "url": "git+https://github.com/LuckDraw/lucky-canvas.git"
13 | },
14 | "keywords": [
15 | "vue3抽奖",
16 | "大转盘抽奖",
17 | "九宫格抽奖",
18 | "抽奖插件",
19 | "js抽奖",
20 | "移动端抽奖",
21 | "canvas抽奖"
22 | ],
23 | "author": "ldq ",
24 | "license": "BSD-3-Clause",
25 | "bugs": {
26 | "url": "https://github.com/LuckDraw/lucky-canvas/issues"
27 | },
28 | "homepage": "https://100px.net",
29 | "files": [
30 | "package.json",
31 | "LICENSE",
32 | "dist",
33 | "index.js",
34 | "umd.min.js"
35 | ],
36 | "devDependencies": {
37 | "@babel/core": "^7.12.3",
38 | "@babel/preset-env": "^7.12.1",
39 | "@rollup/plugin-commonjs": "^16.0.0",
40 | "@rollup/plugin-eslint": "^8.0.1",
41 | "@rollup/plugin-json": "^4.1.0",
42 | "@rollup/plugin-node-resolve": "^10.0.0",
43 | "@rollup/plugin-typescript": "^6.1.0",
44 | "@typescript-eslint/parser": "^4.14.0",
45 | "babel-plugin-external-helpers": "^6.22.0",
46 | "babel-preset-latest": "^6.24.1",
47 | "eslint": "^7.18.0",
48 | "eslint-plugin-prettier": "^3.3.1",
49 | "prettier": "^2.2.1",
50 | "rollup": "^2.33.1",
51 | "rollup-plugin-babel": "^4.4.0",
52 | "rollup-plugin-livereload": "^2.0.0",
53 | "rollup-plugin-serve": "^1.1.0",
54 | "rollup-plugin-terser": "^7.0.2",
55 | "rollup-plugin-typescript2": "^0.29.0",
56 | "tslib": "^2.0.3",
57 | "typescript": "^4.0.5"
58 | },
59 | "dependencies": {}
60 | }
61 |
--------------------------------------------------------------------------------
/rollup.config.build.js:
--------------------------------------------------------------------------------
1 | import { name } from './package.json'
2 | import ts from '@rollup/plugin-typescript'
3 | import json from '@rollup/plugin-json'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import commonjs from '@rollup/plugin-commonjs'
6 | import babel from 'rollup-plugin-babel'
7 | import { terser } from 'rollup-plugin-terser'
8 |
9 | export default {
10 | input: 'src/index.ts',
11 | output: [
12 | {
13 | file: `dist/${name}.cjs.js`,
14 | format: 'cjs',
15 | },
16 | {
17 | file: `dist/${name}.cjs.min.js`,
18 | format: 'cjs',
19 | plugins: [terser()]
20 | },
21 | {
22 | file: `dist/${name}.umd.js`,
23 | format: 'umd',
24 | name: 'LuckyCanvas',
25 | },
26 | {
27 | file: `dist/${name}.umd.min.js`,
28 | format: 'umd',
29 | name: 'LuckyCanvas',
30 | plugins: [terser()]
31 | },
32 | {
33 | file: `umd.min.js`,
34 | format: 'umd',
35 | name: 'LuckyCanvas',
36 | plugins: [terser()]
37 | },
38 | ],
39 | plugins: [
40 | ts(),
41 | json(),
42 | resolve(),
43 | commonjs(),
44 | babel({ exclude: 'node_modules/**' }),
45 | ]
46 | }
47 |
--------------------------------------------------------------------------------
/rollup.config.dev.js:
--------------------------------------------------------------------------------
1 | import path from 'path'
2 | import ts from 'rollup-plugin-typescript2'
3 | import json from '@rollup/plugin-json'
4 | import resolve from '@rollup/plugin-node-resolve'
5 | import commonjs from '@rollup/plugin-commonjs'
6 | import babel from 'rollup-plugin-babel'
7 | import livereload from 'rollup-plugin-livereload'
8 | import serve from 'rollup-plugin-serve'
9 | import eslint from '@rollup/plugin-eslint'
10 |
11 | export default {
12 | input: 'src/index.ts',
13 | output: [
14 | {
15 | file: `examples/dev.js`,
16 | format: 'umd',
17 | name: 'LuckyCanvas',
18 | sourcemap: true,
19 | },
20 | ],
21 | plugins: [
22 | resolve(),
23 | commonjs(),
24 | json(),
25 | // eslint({
26 | // throwOnError: true,
27 | // throwOnWarning: true,
28 | // include: ['src/**'],
29 | // exclude: ['node_modules/**']
30 | // }),
31 | ts({
32 | tsconfig: path.resolve(__dirname, './tsconfig.json'),
33 | extensions: ['.js', '.ts']
34 | }),
35 | babel({ exclude: 'node_modules/**' }),
36 | livereload(),
37 | serve({
38 | open: true,
39 | port: 8888,
40 | contentBase: './examples'
41 | }),
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
1 | export { default as LuckyWheel } from './lib/wheel'
2 | export { default as LuckyGrid } from './lib/grid'
3 | // export { default as LuckyCard } from './lib/card'
4 |
--------------------------------------------------------------------------------
/src/lib/card.ts:
--------------------------------------------------------------------------------
1 | import Lucky from './lucky'
2 | import { ConfigType, UniImageType } from '../types/index'
3 | import LuckyCardConfig, {
4 | MaskType,
5 | DefaultConfigType,
6 | StartCallbackType,
7 | EndCallbackType
8 | } from '../types/card'
9 | import { isExpectType, computePadding, hasBackground } from '../utils/index'
10 | import { drawRoundRect } from '../utils/math'
11 |
12 | export default class LuckyCard extends Lucky {
13 | private mask: MaskType = {}
14 | private defaultConfig: DefaultConfigType = {}
15 | private _defaultConfig = {
16 | percent: 0.5,
17 | cleanZone: { x: 0, y: 0, width: 0, height: 0 }
18 | }
19 | private startCallback?: StartCallbackType
20 | private endCallback?: EndCallbackType
21 | // 是否可以开始游戏
22 | private canPlay: boolean = false
23 | // 鼠标是否按下
24 | private isMouseDown: boolean = false
25 |
26 | /**
27 | * 刮刮乐构造器
28 | * @param config
29 | * @param data
30 | */
31 | constructor (config: ConfigType, data: LuckyCardConfig = {}) {
32 | super(config)
33 | this.initData(data)
34 | this.init()
35 | }
36 |
37 | initData (data: LuckyCardConfig) {
38 | this.$set(this, 'mask', data.mask || [])
39 | this.$set(this, 'startCallback', data.start)
40 | this.$set(this, 'endCallback', data.end)
41 | }
42 |
43 | init () {
44 | super.init()
45 | const { config, ctx } = this
46 | this.canPlay = false
47 | this.draw()
48 | }
49 |
50 | draw () {
51 | const { config, ctx, mask } = this
52 | ctx.globalCompositeOperation = 'source-over'
53 | const background = mask.background
54 | if (hasBackground(background)) {
55 | ctx.fillStyle = background!
56 | ctx.beginPath()
57 | ctx.rect(0, 0, this.boxWidth, this.boxHeight)
58 | ctx.fill()
59 | }
60 | ctx.globalCompositeOperation = 'destination-out'
61 | }
62 |
63 | /**
64 | * 鼠标移动事件
65 | * @param e 事件参数
66 | */
67 | protected handleMouseMove (e: MouseEvent): void {
68 | if (!this.canPlay || !this.isMouseDown) return
69 | const { config, ctx, _defaultConfig } = this
70 | ctx.beginPath()
71 | const radius = 20
72 | const [x, y] = this.conversionAxis(e.offsetX, e.offsetY)
73 | // ctx.clearRect(x - radius, y - radius, radius * 2, radius * 2)
74 | drawRoundRect(ctx, x - radius, y - radius, radius * 2, radius * 2, 15, '#ccc')
75 | ctx.fill()
76 | const ImageData = ctx.getImageData(0, 0, this.boxWidth * config.dpr, this.boxHeight * config.dpr)?.data
77 | let count = 0, len = ImageData.length / 4
78 | for (let i = 1; i <= len; i++) {
79 | if (ImageData[(i - 1) * 4] < 128) count++
80 | }
81 | const percent = +(count / len).toFixed(2)
82 | if (percent > this.getLength(_defaultConfig.percent)) {
83 | this.clean()
84 | this.endCallback?.()
85 | }
86 | }
87 |
88 | /**
89 | * 开始游戏 (调用该方法才可以擦除)
90 | */
91 | public play () {
92 | this.canPlay = true
93 | this.startCallback?.()
94 | }
95 |
96 | /**
97 | * 擦除所有区域
98 | */
99 | public clean () {
100 | const { config, ctx } = this
101 | ctx.clearRect(0, 0, this.boxWidth, this.boxHeight)
102 | }
103 |
104 | /**
105 | * 鼠标按下事件
106 | * @param e 事件参数
107 | */
108 | protected handleMouseDown (e: MouseEvent): void {
109 | this.isMouseDown = true
110 | }
111 |
112 | /**
113 | * 鼠标抬起事件
114 | * @param e 事件参数
115 | */
116 | protected handleMouseUp (e: MouseEvent): void {
117 | this.isMouseDown = false
118 | }
119 |
120 | /**
121 | * 获取相对宽度
122 | */
123 | private getWidth (
124 | width: string | number | undefined,
125 | maxWidth: number = this.boxWidth
126 | ): number {
127 | if (isExpectType(width, 'number')) return (width as number)
128 | if (isExpectType(width, 'string')) return this.changeUnits(width as string, maxWidth)
129 | return 0
130 | }
131 |
132 | /**
133 | * 获取相对高度
134 | */
135 | private getHeight (
136 | height: string | number | undefined,
137 | maxHeight: number = this.boxHeight
138 | ): number {
139 | if (isExpectType(height, 'number')) return (height as number)
140 | if (isExpectType(height, 'string')) return this.changeUnits(height as string, maxHeight)
141 | return 0
142 | }
143 |
144 | /**
145 | * 换算渲染坐标
146 | * @param x
147 | * @param y
148 | */
149 | protected conversionAxis (x: number, y: number): [number, number] {
150 | const { config } = this
151 | return [x / config.dpr, y / config.dpr]
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/src/lib/grid.ts:
--------------------------------------------------------------------------------
1 | import Lucky from './lucky'
2 | import { ConfigType, UniImageType } from '../types/index'
3 | import LuckyGridConfig, {
4 | BlockType, BlockImgType,
5 | PrizeType, PrizeImgType,
6 | ButtonType, ButtonImgType,
7 | CellFontType, CellImgType,
8 | RowsType, ColsType,
9 | CellType,
10 | DefaultConfigType,
11 | DefaultStyleType,
12 | ActiveStyleType,
13 | StartCallbackType,
14 | EndCallbackType,
15 | } from '../types/grid'
16 | import { isExpectType, removeEnter, computePadding, hasBackground } from '../utils/index'
17 | import { drawRoundRect, getLinearGradient } from '../utils/math'
18 | import { quad } from '../utils/tween'
19 |
20 | type ImgObjType = HTMLImageElement | UniImageType
21 |
22 | export default class LuckyGrid extends Lucky {
23 | private rows: RowsType = 3
24 | private cols: ColsType = 3
25 | private blocks: Array = []
26 | private prizes: Array = []
27 | private buttons: Array = []
28 | private button?: ButtonType
29 | private defaultConfig: DefaultConfigType = {}
30 | private _defaultConfig = { // 此处初始化无用, 是为了方便类型推导才加的
31 | gutter: 5,
32 | speed: 20,
33 | accelerationTime: 2500,
34 | decelerationTime: 2500,
35 | }
36 | private defaultStyle: DefaultStyleType = {}
37 | private _defaultStyle = { // 此处初始化无用, 是为了方便类型推导才加的
38 | borderRadius: 20,
39 | fontColor: '#000',
40 | fontSize: '18px',
41 | fontStyle: 'sans-serif',
42 | fontWeight: '400',
43 | lineHeight: '',
44 | background: 'rgba(0,0,0,0)',
45 | shadow: '',
46 | wordWrap: true,
47 | lengthLimit: '90%',
48 | }
49 | private activeStyle: ActiveStyleType = {}
50 | private _activeStyle = { // 此处初始化无用, 是为了方便类型推导才加的
51 | background: '#ffce98',
52 | shadow: '',
53 | fontStyle: '',
54 | fontWeight: '',
55 | fontSize: '',
56 | lineHeight: '',
57 | fontColor: '',
58 | }
59 | private startCallback?: StartCallbackType
60 | private endCallback?: EndCallbackType
61 | private cellWidth = 0 // 格子宽度
62 | private cellHeight = 0 // 格子高度
63 | private startTime = 0 // 开始时间戳
64 | private endTime = 0 // 结束时间戳
65 | private currIndex = 0 // 当前index累加
66 | private stopIndex = 0 // 刻舟求剑
67 | private endIndex = 0 // 停止索引
68 | private demo = false // 是否自动游走
69 | private timer = 0 // 游走定时器
70 | private FPS = 16.6 // 屏幕刷新率
71 | /**
72 | * 中奖索引
73 | * prizeFlag = undefined 时, 处于开始抽奖阶段, 正常旋转
74 | * prizeFlag >= 0 时, 说明stop方法被调用, 并且传入了中奖索引
75 | * prizeFlag === -1 时, 说明stop方法被调用, 并且传入了负值, 本次抽奖无效
76 | */
77 | private prizeFlag: number | undefined = -1
78 | // 所有格子
79 | private cells: CellType[] = []
80 | // 奖品区域几何信息
81 | private prizeArea: { x: number, y: number, w: number, h: number } | undefined
82 | // 图片缓存
83 | private blockImgs: Array<{ defaultImg: ImgObjType }[]> = [[]]
84 | private btnImgs: Array<{ defaultImg: ImgObjType }[]> = [[]]
85 | private prizeImgs: Array<{ defaultImg: ImgObjType, activeImg?: ImgObjType }[]> = []
86 |
87 | /**
88 | * 九宫格构造器
89 | * @param config 元素标识
90 | * @param data 抽奖配置项
91 | */
92 | constructor (config: ConfigType, data: LuckyGridConfig = {}) {
93 | super(config)
94 | if (config.ob) {
95 | this.initData(data)
96 | this.initWatch()
97 | }
98 | this.initComputed()
99 | // 创建前回调函数
100 | config.beforeCreate?.call(this)
101 | const btnImgs = this.buttons.map(btn => btn.imgs)
102 | if (this.button) btnImgs.push(this.button.imgs)
103 | this.init({
104 | blockImgs: this.blocks.map(block => block.imgs),
105 | prizeImgs: this.prizes.map(prize => prize.imgs),
106 | btnImgs,
107 | })
108 | }
109 |
110 | /**
111 | * 初始化数据
112 | * @param data
113 | */
114 | private initData (data: LuckyGridConfig): void {
115 | this.$set(this, 'rows', Number(data.rows) || 3)
116 | this.$set(this, 'cols', Number(data.cols) || 3)
117 | this.$set(this, 'blocks', data.blocks || [])
118 | this.$set(this, 'prizes', data.prizes || [])
119 | this.$set(this, 'buttons', data.buttons || [])
120 | // 临时过渡代码, 升级到2.x即可删除
121 | this.$set(this, 'button', data.button)
122 | this.$set(this, 'defaultConfig', data.defaultConfig || {})
123 | this.$set(this, 'defaultStyle', data.defaultStyle || {})
124 | this.$set(this, 'activeStyle', data.activeStyle || {})
125 | this.$set(this, 'startCallback', data.start)
126 | this.$set(this, 'endCallback', data.end)
127 | }
128 |
129 | /**
130 | * 初始化属性计算
131 | */
132 | private initComputed (): void {
133 | // 默认配置
134 | this.$computed(this, '_defaultConfig', () => {
135 | const config = {
136 | gutter: 5,
137 | speed: 20,
138 | accelerationTime: 2500,
139 | decelerationTime: 2500,
140 | ...this.defaultConfig
141 | }
142 | config.gutter = this.getLength(config.gutter)
143 | config.speed = config.speed / 40
144 | return config
145 | })
146 | // 默认样式
147 | this.$computed(this, '_defaultStyle', () => {
148 | return {
149 | borderRadius: 20,
150 | fontColor: '#000',
151 | fontSize: '18px',
152 | fontStyle: 'sans-serif',
153 | fontWeight: '400',
154 | background: 'rgba(0,0,0,0)',
155 | shadow: '',
156 | wordWrap: true,
157 | lengthLimit: '90%',
158 | ...this.defaultStyle
159 | }
160 | })
161 | // 中奖样式
162 | this.$computed(this, '_activeStyle', () => {
163 | return {
164 | background: '#ffce98',
165 | shadow: '',
166 | ...this.activeStyle
167 | }
168 | })
169 | }
170 |
171 | /**
172 | * 初始化观察者
173 | */
174 | private initWatch (): void {
175 | // 监听 blocks 数据的变化
176 | this.$watch('blocks', (newData: Array) => {
177 | return this.init({ blockImgs: newData.map(block => block.imgs) })
178 | }, { deep: true })
179 | // 监听 prizes 数据的变化
180 | this.$watch('prizes', (newData: Array) => {
181 | return this.init({ prizeImgs: newData.map(prize => prize.imgs) })
182 | }, { deep: true })
183 | // 监听 button 数据的变化
184 | this.$watch('buttons', (newData: Array) => {
185 | const btnImgs = newData.map(btn => btn.imgs)
186 | if (this.button) btnImgs.push(this.button.imgs)
187 | return this.init({ btnImgs })
188 | }, { deep: true })
189 | // 临时过渡代码, 升级到2.x即可删除
190 | this.$watch('button', () => {
191 | const btnImgs = this.buttons.map(btn => btn.imgs)
192 | if (this.button) btnImgs.push(this.button.imgs)
193 | return this.init({ btnImgs })
194 | }, { deep: true })
195 | this.$watch('rows', () => this.init({}))
196 | this.$watch('cols', () => this.init({}))
197 | this.$watch('defaultConfig', () => this.draw(), { deep: true })
198 | this.$watch('defaultStyle', () => this.draw(), { deep: true })
199 | this.$watch('activeStyle', () => this.draw(), { deep: true })
200 | this.$watch('startCallback', () => this.init({}))
201 | this.$watch('endCallback', () => this.init({}))
202 | }
203 |
204 | /**
205 | * 初始化 canvas 抽奖
206 | * @param willUpdateImgs 需要更新的图片
207 | */
208 | public init (willUpdateImgs: {
209 | blockImgs?: Array,
210 | prizeImgs?: Array,
211 | btnImgs?: Array
212 | }): void {
213 | super.init()
214 | const { config, ctx, button } = this
215 | // 初始化前回调函数
216 | config.beforeInit?.call(this)
217 | // 先画一次防止闪烁
218 | this.draw()
219 | // 异步加载图片
220 | Object.keys(willUpdateImgs).forEach(key => {
221 | const imgName = key as 'blockImgs' | 'prizeImgs' | 'btnImgs'
222 | const willUpdate = willUpdateImgs[imgName]
223 | const cellName = {
224 | blockImgs: 'blocks',
225 | prizeImgs: 'prizes',
226 | btnImgs: 'buttons',
227 | }[imgName] as 'blocks' | 'prizes' | 'buttons'
228 | willUpdate && willUpdate.forEach((imgs, cellIndex) => {
229 | imgs && imgs.forEach((imgInfo, imgIndex) => {
230 | this.loadAndCacheImg(cellName, cellIndex, imgName, imgIndex, () => {
231 | this.draw()
232 | })
233 | })
234 | })
235 | })
236 | // 初始化后回调函数
237 | config.afterInit?.call(this)
238 | }
239 |
240 | /**
241 | * canvas点击事件
242 | * @param e 事件参数
243 | */
244 | protected handleClick (e: MouseEvent): void {
245 | const { ctx } = this
246 | ;[
247 | ...this.buttons,
248 | this.button
249 | ].forEach(btn => {
250 | if (!btn) return
251 | const [x, y, width, height] = this.getGeometricProperty([
252 | btn.x, btn.y, btn.col || 1, btn.row || 1
253 | ])
254 | ctx.beginPath()
255 | ctx.rect(x, y, width, height)
256 | if (!ctx.isPointInPath(e.offsetX, e.offsetY)) return
257 | if (this.startTime) return
258 | // 如果btn里有单独的回调方法, 优先触发
259 | if (typeof btn.callback === 'function') btn.callback.call(this, btn)
260 | // 最后触发公共回调
261 | this.startCallback?.(e, btn)
262 | })
263 | }
264 |
265 | /**
266 | * 根据索引单独加载指定图片并缓存
267 | * @param { number } prizeIndex 奖品索引
268 | * @param { number } imgIndex 奖品图片索引
269 | * @param { Function } callBack 图片加载完毕回调
270 | */
271 | private async loadAndCacheImg (
272 | cellName: 'blocks' | 'prizes' | 'buttons',
273 | cellIndex: number,
274 | imgName: 'blockImgs' | 'prizeImgs' | 'btnImgs',
275 | imgIndex: number,
276 | callBack: () => void
277 | ) {
278 | let cell: BlockType | PrizeType | ButtonType = this[cellName][cellIndex]
279 | // 临时过渡代码, 升级到2.x即可删除
280 | if (cellName === 'buttons' && !this.buttons.length && this.button) {
281 | cell = this.button
282 | }
283 | if (!cell || !cell.imgs) return
284 | const imgInfo = cell.imgs[imgIndex]
285 | if (!imgInfo) return
286 | if (!this[imgName][cellIndex]) this[imgName][cellIndex] = []
287 | // 异步加载图片
288 | const request = [
289 | this.loadImg(imgInfo.src, imgInfo),
290 | imgInfo['activeSrc'] && this.loadImg(imgInfo['activeSrc'], imgInfo, '$activeResolve')
291 | ]
292 | Promise.all(request).then(([defaultImg, activeImg]) => {
293 | this[imgName][cellIndex][imgIndex] = { defaultImg, activeImg } as {
294 | defaultImg: ImgObjType,
295 | activeImg?: ImgObjType
296 | }
297 | callBack.call(this)
298 | }).catch(err => {
299 | console.error(`${cellName}[${cellIndex}].imgs[${imgIndex}] ${err}`)
300 | })
301 | }
302 |
303 | /**
304 | * 计算图片的渲染宽高
305 | * @param imgObj 图片标签元素
306 | * @param imgInfo 图片信息
307 | * @param cell 格子信息
308 | * @return [渲染宽度, 渲染高度]
309 | */
310 | private computedWidthAndHeight (
311 | imgObj: ImgObjType,
312 | imgInfo: CellImgType,
313 | cell: CellType
314 | ): [number, number] {
315 | // 根据配置的样式计算图片的真实宽高
316 | if (!imgInfo.width && !imgInfo.height) {
317 | // 如果没有配置宽高, 则使用图片本身的宽高
318 | return [imgObj.width, imgObj.height]
319 | } else if (imgInfo.width && !imgInfo.height) {
320 | // 如果只填写了宽度, 没填写高度
321 | let trueWidth = this.getWidth(imgInfo.width, cell.col)
322 | // 那高度就随着宽度进行等比缩放
323 | return [trueWidth, imgObj.height * (trueWidth / imgObj.width)]
324 | } else if (!imgInfo.width && imgInfo.height) {
325 | // 如果只填写了宽度, 没填写高度
326 | let trueHeight = this.getHeight(imgInfo.height, cell.row)
327 | // 那宽度就随着高度进行等比缩放
328 | return [imgObj.width * (trueHeight / imgObj.height), trueHeight]
329 | }
330 | // 如果宽度和高度都填写了, 就分别计算
331 | return [
332 | this.getWidth(imgInfo.width, cell.col),
333 | this.getHeight(imgInfo.height, cell.row)
334 | ]
335 | }
336 |
337 | /**
338 | * 绘制九宫格抽奖
339 | */
340 | protected draw (): void {
341 | const { config, ctx, _defaultConfig, _defaultStyle, _activeStyle } = this
342 | // 触发绘制前回调
343 | config.beforeDraw?.call(this, ctx)
344 | // 清空画布
345 | ctx.clearRect(0, 0, this.boxWidth, this.boxHeight)
346 | // 合并奖品和按钮
347 | this.cells = [
348 | ...this.prizes,
349 | ...this.buttons
350 | ]
351 | if (this.button) this.cells.push(this.button)
352 | this.cells.forEach(cell => {
353 | cell.col = cell.col || 1
354 | cell.row = cell.row || 1
355 | })
356 | // 计算获取奖品区域的几何信息
357 | this.prizeArea = this.blocks.reduce(({x, y, w, h}, block) => {
358 | const [paddingTop, paddingBottom, paddingLeft, paddingRight] = computePadding(block)
359 | const r = block.borderRadius ? this.getLength(block.borderRadius) : 0
360 | // 绘制边框
361 | const background = block.background || _defaultStyle.background
362 | if (hasBackground(background)) {
363 | drawRoundRect(ctx, x, y, w, h, r, this.handleBackground(x, y, w, h, background))
364 | }
365 | return {
366 | x: x + paddingLeft,
367 | y: y + paddingTop,
368 | w: w - paddingLeft - paddingRight,
369 | h: h - paddingTop - paddingBottom
370 | }
371 | }, { x: 0, y: 0, w: this.boxWidth, h: this.boxHeight })
372 | // 计算单一奖品格子的宽度和高度
373 | this.cellWidth = (this.prizeArea.w - _defaultConfig.gutter * (this.cols - 1)) / this.cols
374 | this.cellHeight = (this.prizeArea.h - _defaultConfig.gutter * (this.rows - 1)) / this.rows
375 | // 绘制所有格子
376 | this.cells.forEach((cell, cellIndex) => {
377 | let [x, y, width, height] = this.getGeometricProperty([cell.x, cell.y, cell.col, cell.row])
378 | // 默认不显示中奖标识
379 | let isActive = false
380 | // 只要 prizeFlag 不是负数, 就显示中奖标识
381 | if (this.prizeFlag === void 0 || this.prizeFlag > -1) {
382 | isActive = cellIndex === this.currIndex % this.prizes.length >> 0
383 | }
384 | // 绘制背景色
385 | const background = isActive ? _activeStyle.background : (cell.background || _defaultStyle.background)
386 | if (hasBackground(background)) {
387 | // 处理阴影 (暂时先用any, 这里后续要优化)
388 | const shadow: any = (
389 | isActive ? _activeStyle.shadow! : (cell.shadow || _defaultStyle.shadow!)
390 | )
391 | .replace(/px/g, '') // 清空px字符串
392 | .split(',')[0].split(' ') // 防止有人声明多个阴影, 截取第一个阴影
393 | .map((n, i) => i < 3 ? Number(n) : n) // 把数组的前三个值*像素比
394 | // 绘制阴影
395 | if (shadow.length === 4) {
396 | ctx.shadowColor = shadow[3]
397 | ctx.shadowOffsetX = shadow[0] * config.dpr
398 | ctx.shadowOffsetY = shadow[1] * config.dpr
399 | ctx.shadowBlur = shadow[2]
400 | // 修正(格子+阴影)的位置, 这里使用逗号运算符
401 | shadow[0] > 0 ? (width -= shadow[0]) : (width += shadow[0], x -= shadow[0])
402 | shadow[1] > 0 ? (height -= shadow[1]) : (height += shadow[1], y -= shadow[1])
403 | }
404 | drawRoundRect(
405 | ctx, x, y, width, height,
406 | this.getLength(cell.borderRadius ? cell.borderRadius : _defaultStyle.borderRadius),
407 | this.handleBackground(x, y, width, height, background)
408 | )
409 | // 清空阴影
410 | ctx.shadowColor = 'rgba(0, 0, 0, 0)'
411 | ctx.shadowOffsetX = 0
412 | ctx.shadowOffsetY = 0
413 | ctx.shadowBlur = 0
414 | }
415 | // 修正图片缓存
416 | let cellName = 'prizeImgs'
417 | if (cellIndex >= this.prizes.length) {
418 | cellName = 'btnImgs'
419 | cellIndex -= this.prizes.length
420 | }
421 | // 绘制图片
422 | cell.imgs && cell.imgs.forEach((imgInfo, imgIndex) => {
423 | if (!this[cellName][cellIndex]) return
424 | const cellImg = this[cellName][cellIndex][imgIndex]
425 | if (!cellImg) return
426 | const renderImg = (isActive && cellImg['activeImg']) || cellImg.defaultImg
427 | if (!renderImg) return
428 | const [trueWidth, trueHeight] = this.computedWidthAndHeight(renderImg, imgInfo, cell)
429 | const [xAxis, yAxis] = [
430 | x + this.getOffsetX(trueWidth, cell.col),
431 | y + this.getHeight(imgInfo.top, cell.row)
432 | ]
433 | this.drawImage(renderImg, xAxis, yAxis, trueWidth, trueHeight)
434 | })
435 | // 绘制文字
436 | cell.fonts && cell.fonts.forEach(font => {
437 | // 字体样式
438 | let style = isActive && _activeStyle.fontStyle
439 | ? _activeStyle.fontStyle
440 | : (font.fontStyle || _defaultStyle.fontStyle)
441 | // 字体加粗
442 | let fontWeight = isActive && _activeStyle.fontWeight
443 | ? _activeStyle.fontWeight
444 | : (font.fontWeight || _defaultStyle.fontWeight)
445 | // 字体大小
446 | let size = isActive && _activeStyle.fontSize
447 | ? this.getLength(_activeStyle.fontSize)
448 | : this.getLength(font.fontSize || _defaultStyle.fontSize)
449 | // 字体行高
450 | const lineHeight = isActive && _activeStyle.lineHeight
451 | ? _activeStyle.lineHeight
452 | : font.lineHeight || _defaultStyle.lineHeight || font.fontSize || _defaultStyle.fontSize
453 | ctx.font = `${fontWeight} ${size >> 0}px ${style}`
454 | ctx.fillStyle = (isActive && _activeStyle.fontColor) ? _activeStyle.fontColor : (font.fontColor || _defaultStyle.fontColor)
455 | let lines = [], text = String(font.text)
456 | // 计算文字换行
457 | if (Object.prototype.hasOwnProperty.call(font, 'wordWrap') ? font.wordWrap : _defaultStyle.wordWrap) {
458 | text = removeEnter(text)
459 | let str = ''
460 | for (let i = 0; i < text.length; i++) {
461 | str += text[i]
462 | let currWidth = ctx.measureText(str).width
463 | let maxWidth = this.getWidth(font.lengthLimit || _defaultStyle.lengthLimit, cell.col)
464 | if (currWidth > maxWidth) {
465 | lines.push(str.slice(0, -1))
466 | str = text[i]
467 | }
468 | }
469 | if (str) lines.push(str)
470 | if (!lines.length) lines.push(text)
471 | } else {
472 | lines = text.split('\n')
473 | }
474 | lines.forEach((line, lineIndex) => {
475 | ctx.fillText(
476 | line,
477 | x + this.getOffsetX(ctx.measureText(line).width, cell.col),
478 | y + this.getHeight(font.top, cell.row) + (lineIndex + 1) * this.getLength(lineHeight)
479 | )
480 | })
481 | })
482 | })
483 | // 触发绘制后回调
484 | config.afterDraw?.call(this, ctx)
485 | }
486 |
487 | /**
488 | * 处理背景色
489 | * @param x
490 | * @param y
491 | * @param width
492 | * @param height
493 | * @param background
494 | * @param isActive
495 | */
496 | private handleBackground (
497 | x: number,
498 | y: number,
499 | width: number,
500 | height: number,
501 | background: string,
502 | ) {
503 | const { ctx } = this
504 | // 处理线性渐变
505 | if (background.includes('linear-gradient')) {
506 | background = getLinearGradient(ctx, x, y, width, height, background)
507 | }
508 | return background
509 | }
510 |
511 | /**
512 | * 对外暴露: 开始抽奖方法
513 | */
514 | public play (): void {
515 | const { clearInterval } = this.config
516 | if (this.startTime) return
517 | clearInterval(this.timer)
518 | this.startTime = Date.now()
519 | this.prizeFlag = void 0
520 | this.run()
521 | }
522 |
523 | /**
524 | * 对外暴露: 缓慢停止方法
525 | * @param index 中奖索引
526 | */
527 | public stop (index: number): void {
528 | // 判断 prizeFlag 是否等于 -1
529 | this.prizeFlag = index < 0 ? -1 : index % this.prizes.length
530 | // 如果是 -1 就初始化状态
531 | if (this.prizeFlag === -1) {
532 | this.currIndex = 0
533 | this.draw()
534 | }
535 | }
536 |
537 | /**
538 | * 实际开始执行方法
539 | * @param num 记录帧动画执行多少次
540 | */
541 | private run (num: number = 0): void {
542 | const { rAF, currIndex, prizes, prizeFlag, startTime, _defaultConfig } = this
543 | let interval = Date.now() - startTime
544 | // 先完全旋转, 再停止
545 | if (interval >= _defaultConfig.accelerationTime && prizeFlag !== void 0) {
546 | // 记录帧率
547 | this.FPS = interval / num
548 | // 记录开始停止的时间戳
549 | this.endTime = Date.now()
550 | // 记录开始停止的索引
551 | this.stopIndex = currIndex
552 | // 测算最终停止的索引
553 | let i = 0
554 | while (++i) {
555 | const endIndex = prizes.length * i + prizeFlag - (currIndex >> 0)
556 | let currSpeed = quad.easeOut(this.FPS, this.stopIndex, endIndex, _defaultConfig.decelerationTime) - this.stopIndex
557 | if (currSpeed > _defaultConfig.speed) {
558 | this.endIndex = endIndex
559 | break
560 | }
561 | }
562 | return this.slowDown()
563 | }
564 | this.currIndex = (currIndex + quad.easeIn(interval, 0.1, _defaultConfig.speed, _defaultConfig.accelerationTime)) % prizes.length
565 | this.draw()
566 | rAF(this.run.bind(this, num + 1))
567 | }
568 |
569 | /**
570 | * 缓慢停止的方法
571 | */
572 | private slowDown (): void {
573 | const { rAF, prizes, prizeFlag, stopIndex, endIndex, _defaultConfig } = this
574 | let interval = Date.now() - this.endTime
575 | // 如果等于 -1 就直接停止游戏
576 | if (prizeFlag === -1) return (this.startTime = 0, void 0)
577 | if (interval > _defaultConfig.decelerationTime) {
578 | this.startTime = 0
579 | this.endCallback?.({...prizes.find((prize, index) => index === prizeFlag)})
580 | return
581 | }
582 | this.currIndex = quad.easeOut(interval, stopIndex, endIndex, _defaultConfig.decelerationTime) % prizes.length
583 | this.draw()
584 | rAF(this.slowDown.bind(this))
585 | }
586 |
587 | /**
588 | * 开启中奖标识自动游走
589 | */
590 | public walk (): void {
591 | const { setInterval, clearInterval } = this.config
592 | clearInterval(this.timer)
593 | this.timer = setInterval(() => {
594 | this.currIndex += 1
595 | this.draw()
596 | }, 1300)
597 | }
598 |
599 | /**
600 | * 计算奖品格子的几何属性
601 | * @param { array } [...矩阵坐标, col, row]
602 | * @return { array } [...真实坐标, width, height]
603 | */
604 | private getGeometricProperty ([x, y, col, row]: number[]) {
605 | const { cellWidth, cellHeight } = this
606 | const gutter = this._defaultConfig.gutter
607 | let res = [
608 | this.prizeArea!.x + (cellWidth + gutter) * x,
609 | this.prizeArea!.y + (cellHeight + gutter) * y
610 | ]
611 | col && row && res.push(
612 | cellWidth * col + gutter * (col - 1),
613 | cellHeight * row + gutter * (row - 1),
614 | )
615 | return res
616 | }
617 |
618 | /**
619 | * 转换并获取宽度
620 | * @param width 将要转换的宽度
621 | * @param col 横向合并的格子
622 | * @return 返回相对宽度
623 | */
624 | private getWidth (
625 | width: string | number | undefined,
626 | col: number = 1
627 | ): number {
628 | if (isExpectType(width, 'number')) return (width as number)
629 | if (isExpectType(width, 'string')) return this.changeUnits(
630 | width as string,
631 | this.cellWidth * col + this._defaultConfig.gutter * (col - 1)
632 | )
633 | return 0
634 | }
635 |
636 | /**
637 | * 转换并获取高度
638 | * @param height 将要转换的高度
639 | * @param row 纵向合并的格子
640 | * @return 返回相对高度
641 | */
642 | private getHeight (
643 | height: string | number | undefined,
644 | row: number = 1
645 | ): number {
646 | if (isExpectType(height, 'number')) return (height as number)
647 | if (isExpectType(height, 'string')) return this.changeUnits(
648 | height as string,
649 | this.cellHeight * row + this._defaultConfig.gutter * (row - 1)
650 | )
651 | return 0
652 | }
653 |
654 | /**
655 | * 获取相对(居中)X坐标
656 | * @param width
657 | * @param col
658 | */
659 | private getOffsetX (width: number, col = 1): number {
660 | return (this.cellWidth * col + this._defaultConfig.gutter * (col - 1) - width) / 2
661 | }
662 |
663 | /**
664 | * 换算渲染坐标
665 | * @param x
666 | * @param y
667 | */
668 | protected conversionAxis (x: number, y: number): [number, number] {
669 | const { config } = this
670 | return [x / config.dpr, y / config.dpr]
671 | }
672 | }
673 |
--------------------------------------------------------------------------------
/src/lib/lucky.ts:
--------------------------------------------------------------------------------
1 | import '../utils/polyfill'
2 | import { isExpectType } from '../utils/index'
3 | import { name, version } from '../../package.json'
4 | import { ConfigType, ImgType, UniImageType } from '../types/index'
5 | import { defineReactive } from '../observer'
6 | import Watcher, { WatchOptType } from '../observer/watcher'
7 |
8 | export default class Lucky {
9 | protected readonly config: ConfigType
10 | protected readonly ctx: CanvasRenderingContext2D
11 | protected htmlFontSize: number = 16
12 | protected rAF: Function = function () {}
13 | protected boxWidth: number = 0
14 | protected boxHeight: number = 0
15 |
16 | /**
17 | * 公共构造器
18 | * @param config
19 | */
20 | constructor (config: string | HTMLDivElement | ConfigType) {
21 | // 先初始化 fontSize 以防后面有 rem 单位
22 | this.setHTMLFontSize()
23 | /* eslint-disable */
24 | // 兼容代码开始: 为了处理 v1.0.6 版本在这里传入了一个 dom
25 | if (typeof config === 'string') config = { el: config } as ConfigType
26 | else if (config.nodeType === 1) config = { el: '', divElement: config } as ConfigType
27 | config = config as ConfigType
28 | /* eslint-enable */
29 | this.config = config
30 | // 拿到 config 即可设置 dpr
31 | this.setDpr()
32 | // 初始化 window 方法
33 | this.initWindowFunction()
34 | if (!config.flag) config.flag = 'WEB'
35 | if (!Object.prototype.hasOwnProperty.call(config, 'ob')) config.ob = true
36 | if (config.el) config.divElement = document.querySelector(config.el) as HTMLDivElement
37 | let boxWidth = 0, boxHeight = 0
38 | // 如果存在父盒子, 就创建canvas标签
39 | if (config.divElement) {
40 | // 无论盒子内有没有canvas都执行覆盖逻辑
41 | config.canvasElement = document.createElement('canvas')
42 | config.divElement.appendChild(config.canvasElement)
43 | }
44 | // 初始化宽高
45 | this.resetWidthAndHeight()
46 | // 获取 canvas 上下文
47 | if (config.canvasElement) {
48 | config.ctx = config.canvasElement.getContext('2d')!
49 | // 添加版本信息到标签上, 方便定位版本问题
50 | config.canvasElement.setAttribute('package', `${name}@${version}`)
51 | config.canvasElement.addEventListener('click', e => this.handleClick(e))
52 | config.canvasElement.addEventListener('mousemove', e => this.handleMouseMove(e))
53 | config.canvasElement.addEventListener('mousedown', e => this.handleMouseDown(e))
54 | config.canvasElement.addEventListener('mouseup', e => this.handleMouseUp(e))
55 | }
56 | this.ctx = config.ctx as CanvasRenderingContext2D
57 | // 如果最后得不到 canvas 上下文那就无法进行绘制
58 | if (!config.ctx) {
59 | console.error('无法获取到 CanvasContext2D')
60 | return
61 | }
62 | if (!this.boxWidth || !this.boxHeight) {
63 | console.error('无法获取到宽度或高度')
64 | return
65 | }
66 | }
67 |
68 | /**
69 | * 初始化方法
70 | */
71 | public init (willUpdateImgs?: object) {
72 | this.setDpr()
73 | this.setHTMLFontSize()
74 | this.resetWidthAndHeight()
75 | this.zoomCanvas()
76 | }
77 |
78 | /**
79 | * 鼠标点击事件
80 | * @param e 事件参数
81 | */
82 | protected handleClick (e: MouseEvent): void {}
83 |
84 | /**
85 | * 鼠标按下事件
86 | * @param e 事件参数
87 | */
88 | protected handleMouseDown (e: MouseEvent): void {}
89 |
90 | /**
91 | * 鼠标抬起事件
92 | * @param e 事件参数
93 | */
94 | protected handleMouseUp (e: MouseEvent): void {}
95 |
96 | /**
97 | * 鼠标移动事件
98 | * @param e 事件参数
99 | */
100 | protected handleMouseMove (e: MouseEvent): void {}
101 |
102 | /**
103 | * 换算坐标
104 | */
105 | protected conversionAxis (x: number, y: number): [number, number] {
106 | return [0, 0]
107 | }
108 |
109 | /**
110 | * 设备像素比
111 | * window 环境下自动获取, 其余环境手动传入
112 | */
113 | protected setDpr (): void {
114 | const { config } = this
115 | if (config.dpr) {
116 | // 优先使用 config 传入的 dpr
117 | } else if (window) {
118 | (window as any).dpr = config.dpr = window.devicePixelRatio || 1
119 | } else if (!config.dpr) {
120 | console.error(config, '未传入 dpr 可能会导致绘制异常')
121 | }
122 | }
123 |
124 | /**
125 | * 根标签的字体大小
126 | */
127 | protected setHTMLFontSize (): void {
128 | if (!window) return
129 | this.htmlFontSize = +window.getComputedStyle(document.documentElement).fontSize.slice(0, -2)
130 | }
131 |
132 | /**
133 | * 重置盒子和canvas的宽高
134 | */
135 | private resetWidthAndHeight (): void {
136 | const { config } = this
137 | // 如果是浏览器环境并且存在盒子
138 | let boxWidth = 0, boxHeight = 0
139 | if (config.divElement) {
140 | boxWidth = config.divElement.offsetWidth
141 | boxHeight = config.divElement.offsetHeight
142 | }
143 | // 如果 config 上面没有宽高, 就从 style 上面取
144 | this.boxWidth = this.getLength(config.width) || boxWidth
145 | this.boxHeight = this.getLength(config.height) || boxHeight
146 | // 重新把宽高赋给盒子
147 | if (config.divElement) {
148 | config.divElement.style.overflow = 'hidden'
149 | config.divElement.style.width = this.boxWidth + 'px'
150 | config.divElement.style.height = this.boxHeight + 'px'
151 | }
152 | }
153 |
154 | /**
155 | * 从 window 对象上获取一些方法
156 | */
157 | private initWindowFunction (): void {
158 | const { config } = this
159 | if (window) {
160 | this.rAF = (function () {
161 | return window.requestAnimationFrame ||
162 | window.webkitRequestAnimationFrame ||
163 | window['mozRequestAnimationFrame'] ||
164 | function (callback) {
165 | window.setTimeout(callback, 1000 / 60)
166 | }
167 | })()
168 | config.setTimeout = window.setTimeout
169 | config.setInterval = window.setInterval
170 | config.clearTimeout = window.clearTimeout
171 | config.clearInterval = window.clearInterval
172 | return
173 | }
174 | if (config.rAF) {
175 | // 优先使用帧动画
176 | this.rAF = config.rAF
177 | } else if (config.setTimeout) {
178 | // 其次使用定时器
179 | const timeout = config.setTimeout
180 | this.rAF = (callback: Function): number => timeout(callback, 16.7)
181 | } else {
182 | // 如果config里面没有提供, 那就假设全局方法存在setTimeout
183 | this.rAF = (callback: Function): number => setTimeout(callback, 16.7)
184 | }
185 | }
186 |
187 | /**
188 | * 根据 dpr 缩放 canvas 并处理位移
189 | */
190 | protected zoomCanvas (): void {
191 | const { config, ctx } = this
192 | const { canvasElement, dpr } = config
193 | const [width, height] = [this.boxWidth * dpr, this.boxHeight * dpr]
194 | const compute = (len: number) => (len * dpr - len) / (len * dpr) * (dpr / 2) * 100
195 | if (!canvasElement) return
196 | canvasElement.width = width
197 | canvasElement.height = height
198 | canvasElement.style.width = `${width}px`
199 | canvasElement.style.height = `${height}px`
200 | canvasElement.style.transform = `scale(${1 / dpr}) translate(${-compute(width)}%, ${-compute(height)}%)`
201 | ctx.scale(dpr, dpr)
202 | }
203 |
204 | /**
205 | * 异步加载图片并返回图片的几何信息
206 | * @param src 图片路径
207 | * @param info 图片信息
208 | */
209 | protected loadImg (
210 | src: string,
211 | info: ImgType,
212 | resolveName = '$resolve'
213 | ): Promise {
214 | return new Promise((resolve, reject) => {
215 | if (!src) reject(`=> '${info.src}' 不能为空或不合法`)
216 | if (this.config.flag === 'WEB') {
217 | let imgObj = new Image()
218 | imgObj.src = src
219 | imgObj.onload = () => resolve(imgObj)
220 | imgObj.onerror = () => reject(`=> '${info.src}' 图片加载失败`)
221 | } else {
222 | // 其余平台向外暴露, 交给外部自行处理
223 | info[resolveName] = resolve
224 | return
225 | }
226 | })
227 | }
228 |
229 | /**
230 | * 公共绘制图片的方法
231 | * @param imgObj 图片对象
232 | * @param xAxis x轴位置
233 | * @param yAxis y轴位置
234 | * @param width 渲染宽度
235 | * @param height 渲染高度
236 | */
237 | protected drawImage (
238 | imgObj: HTMLImageElement | UniImageType,
239 | xAxis: number,
240 | yAxis: number,
241 | width: number,
242 | height: number
243 | ): void {
244 | let drawImg, { config, ctx } = this
245 | if (['WEB', 'MP-WX'].includes(config.flag)) {
246 | // 浏览器中直接绘制标签即可
247 | drawImg = imgObj
248 | } else if (['UNI-H5', 'UNI-MP', 'TARO-H5', 'TARO-MP'].includes(config.flag)) {
249 | // 小程序中直接绘制一个路径
250 | drawImg = (imgObj as UniImageType).path
251 | }
252 | return ctx.drawImage((drawImg as CanvasImageSource), xAxis, yAxis, width, height)
253 | }
254 |
255 | /**
256 | * 获取长度
257 | * @param length 将要转换的长度
258 | * @return 返回长度
259 | */
260 | protected getLength (length: string | number | undefined): number {
261 | if (isExpectType(length, 'number')) return length as number
262 | if (isExpectType(length, 'string')) return this.changeUnits(length as string)
263 | return 0
264 | }
265 |
266 | /**
267 | * 转换单位
268 | * @param { string } value 将要转换的值
269 | * @param { number } denominator 分子
270 | * @return { number } 返回新的字符串
271 | */
272 | protected changeUnits (value: string, denominator = 1): number {
273 | return Number(value.replace(/^([-]*[0-9.]*)([a-z%]*)$/, (value, num, unit) => {
274 | const unitFunc = {
275 | '%': (n: number) => n * (denominator / 100),
276 | 'px': (n: number) => n * 1,
277 | 'rem': (n: number) => n * this.htmlFontSize,
278 | }[unit]
279 | if (unitFunc) return unitFunc(num)
280 | // 如果找不到默认单位, 就交给外面处理
281 | const otherUnitFunc = this.config.unitFunc
282 | return otherUnitFunc ? otherUnitFunc(num, unit) : num
283 | }))
284 | }
285 |
286 | /**
287 | * 添加一个新的响应式数据 (临时)
288 | * @param data 数据
289 | * @param key 属性
290 | * @param value 新值
291 | */
292 | public $set (data: object, key: string | number, value: any) {
293 | if (!data || typeof data !== 'object') return
294 | defineReactive(data, key, value)
295 | }
296 |
297 | /**
298 | * 添加一个属性计算 (临时)
299 | * @param data 源数据
300 | * @param key 属性名
301 | * @param callback 回调函数
302 | */
303 | protected $computed (data: object, key: string, callback: Function) {
304 | Object.defineProperty(data, key, {
305 | get: () => {
306 | return callback.call(this)
307 | }
308 | })
309 | }
310 |
311 | /**
312 | * 添加一个观察者 create user watcher
313 | * @param expr 表达式
314 | * @param handler 回调函数
315 | * @param watchOpt 配置参数
316 | * @return 卸载当前观察者的函数 (暂未返回)
317 | */
318 | protected $watch (
319 | expr: string | Function,
320 | handler: Function | WatchOptType,
321 | watchOpt: WatchOptType = {}
322 | ): Function {
323 | if (typeof handler === 'object') {
324 | watchOpt = handler
325 | handler = watchOpt.handler!
326 | }
327 | // 创建 user watcher
328 | const watcher = new Watcher(this, expr, handler, watchOpt)
329 | // 判断是否需要初始化时触发回调
330 | if (watchOpt.immediate) {
331 | handler.call(this, watcher.value)
332 | }
333 | // 返回一个卸载当前观察者的函数
334 | return function unWatchFn () {}
335 | }
336 | }
337 |
--------------------------------------------------------------------------------
/src/lib/wheel.ts:
--------------------------------------------------------------------------------
1 | import Lucky from './lucky'
2 | import { ConfigType } from '../types/index'
3 | import LuckyWheelConfig, {
4 | BlockType, BlockImgType,
5 | PrizeType, PrizeImgType,
6 | ButtonType, ButtonImgType,
7 | DefaultConfigType,
8 | DefaultStyleType,
9 | StartCallbackType,
10 | EndCallbackType
11 | } from '../types/wheel'
12 | import { FontType, ImgType, UniImageType } from '../types/index'
13 | import { isExpectType, removeEnter, hasBackground } from '../utils/index'
14 | import { getAngle, drawSector } from '../utils/math'
15 | import * as Tween from '../utils/tween'
16 |
17 | export default class LuckyWheel extends Lucky {
18 | private blocks: Array = []
19 | private prizes: Array = []
20 | private buttons: Array = []
21 | private defaultConfig: DefaultConfigType = {}
22 | private _defaultConfig = {
23 | gutter: '0px',
24 | offsetDegree: 0,
25 | speed: 20,
26 | speedFunction: 'quad',
27 | accelerationTime: 2500,
28 | decelerationTime: 2500,
29 | stopRange: 0.8,
30 | }
31 | private defaultStyle: DefaultStyleType = {}
32 | private _defaultStyle = {
33 | fontSize: '18px',
34 | fontColor: '#000',
35 | fontStyle: 'sans-serif',
36 | fontWeight: '400',
37 | lineHeight: '',
38 | background: 'rgba(0,0,0,0)',
39 | wordWrap: true,
40 | lengthLimit: '90%',
41 | }
42 | private startCallback?: StartCallbackType
43 | private endCallback?: EndCallbackType
44 | private Radius = 0 // 大转盘半径
45 | private prizeRadius = 0 // 奖品区域半径
46 | private prizeDeg = 0 // 奖品数学角度
47 | private prizeRadian = 0 // 奖品运算角度
48 | private rotateDeg = 0 // 转盘旋转角度
49 | private maxBtnRadius = 0 // 最大按钮半径
50 | private startTime = 0 // 开始时间戳
51 | private endTime = 0 // 停止时间戳
52 | private stopDeg = 0 // 刻舟求剑
53 | private endDeg = 0 // 停止角度
54 | private FPS = 16.6 // 屏幕刷新率
55 | /**
56 | * 中奖索引
57 | * prizeFlag = undefined 时, 处于开始抽奖阶段, 正常旋转
58 | * prizeFlag >= 0 时, 说明stop方法被调用, 并且传入了中奖索引
59 | * prizeFlag === -1 时, 说明stop方法被调用, 并且传入了负值, 本次抽奖无效
60 | */
61 | private prizeFlag: number | undefined
62 | private blockImgs: Array = [[]]
63 | private prizeImgs: Array = [[]]
64 | private btnImgs: Array = [[]]
65 |
66 | /**
67 | * 大转盘构造器
68 | * @param config 元素标识
69 | * @param data 抽奖配置项
70 | */
71 | constructor (config: ConfigType, data: LuckyWheelConfig = {}) {
72 | super(config)
73 | if (config.ob) {
74 | this.initData(data)
75 | this.initWatch()
76 | }
77 | this.initComputed()
78 | // 创建前回调函数
79 | config.beforeCreate?.call(this)
80 | // 收集首次渲染的图片
81 | this.init({
82 | blockImgs: this.blocks.map(block => block.imgs),
83 | prizeImgs: this.prizes.map(prize => prize.imgs),
84 | btnImgs: this.buttons.map(btn => btn.imgs),
85 | })
86 | }
87 |
88 | /**
89 | * 初始化数据
90 | * @param data
91 | */
92 | private initData (data: LuckyWheelConfig): void {
93 | this.$set(this, 'blocks', data.blocks || [])
94 | this.$set(this, 'prizes', data.prizes || [])
95 | this.$set(this, 'buttons', data.buttons || [])
96 | this.$set(this, 'defaultConfig', data.defaultConfig || {})
97 | this.$set(this, 'defaultStyle', data.defaultStyle || {})
98 | this.$set(this, 'startCallback', data.start)
99 | this.$set(this, 'endCallback', data.end)
100 | }
101 |
102 | /**
103 | * 初始化属性计算
104 | */
105 | private initComputed () {
106 | // 默认配置
107 | this.$computed(this, '_defaultConfig', () => {
108 | const config = {
109 | gutter: '0px',
110 | offsetDegree: 0,
111 | speed: 20,
112 | speedFunction: 'quad',
113 | accelerationTime: 2500,
114 | decelerationTime: 2500,
115 | stopRange: 0.8,
116 | ...this.defaultConfig
117 | }
118 | return config
119 | })
120 | // 默认样式
121 | this.$computed(this, '_defaultStyle', () => {
122 | const style = {
123 | fontSize: '18px',
124 | fontColor: '#000',
125 | fontStyle: 'sans-serif',
126 | fontWeight: '400',
127 | background: 'rgba(0,0,0,0)',
128 | wordWrap: true,
129 | lengthLimit: '90%',
130 | ...this.defaultStyle
131 | }
132 | return style
133 | })
134 | }
135 |
136 | /**
137 | * 初始化观察者
138 | */
139 | private initWatch () {
140 | // 观察 blocks 变化收集图片
141 | this.$watch('blocks', (newData: Array) => {
142 | return this.init({ blockImgs: newData.map(cell => cell.imgs) })
143 | }, { deep: true })
144 | // 观察 prizes 变化收集图片
145 | this.$watch('prizes', (newData: Array) => {
146 | return this.init({ prizeImgs: newData.map(cell => cell.imgs) })
147 | }, { deep: true })
148 | // 观察 buttons 变化收集图片
149 | this.$watch('buttons', (newData: Array) => {
150 | return this.init({ btnImgs: newData.map(cell => cell.imgs) })
151 | }, { deep: true })
152 | this.$watch('defaultConfig', () => this.draw(), { deep: true })
153 | this.$watch('defaultStyle', () => this.draw(), { deep: true })
154 | this.$watch('startCallback', () => this.init({}))
155 | this.$watch('endCallback', () => this.init({}))
156 | }
157 |
158 | /**
159 | * 初始化 canvas 抽奖
160 | * @param { willUpdateImgs } willUpdateImgs 需要更新的图片
161 | */
162 | public init (willUpdateImgs: {
163 | blockImgs?: Array
164 | prizeImgs?: Array
165 | btnImgs?: Array
166 | }): void {
167 | super.init()
168 | const { config, ctx } = this
169 | this.Radius = Math.min(this.boxWidth, this.boxHeight) / 2
170 | // 初始化前回调函数
171 | config.beforeInit?.call(this)
172 | ctx.translate(this.Radius, this.Radius)
173 | this.draw() // 先画一次, 防止闪烁
174 | this.draw() // 再画一次, 拿到正确的按钮轮廓
175 | // 异步加载图片
176 | Object.keys(willUpdateImgs).forEach(key => {
177 | const imgName = key as 'blockImgs' | 'prizeImgs' | 'btnImgs'
178 | const cellName = {
179 | blockImgs: 'blocks',
180 | prizeImgs: 'prizes',
181 | btnImgs: 'buttons',
182 | }[imgName] as 'blocks' | 'prizes' | 'buttons'
183 | const willUpdate = willUpdateImgs[imgName]
184 | willUpdate && willUpdate.forEach((imgs, cellIndex) => {
185 | imgs && imgs.forEach((imgInfo, imgIndex) => {
186 | this.loadAndCacheImg(cellName, cellIndex, imgName, imgIndex, () => {
187 | this.draw()
188 | })
189 | })
190 | })
191 | })
192 | // 初始化后回调函数
193 | config.afterInit?.call(this)
194 | }
195 |
196 | /**
197 | * canvas点击事件
198 | * @param e 事件参数
199 | */
200 | protected handleClick (e: MouseEvent): void {
201 | const { ctx } = this
202 | ctx.beginPath()
203 | ctx.arc(0, 0, this.maxBtnRadius, 0, Math.PI * 2, false)
204 | if (!ctx.isPointInPath(e.offsetX, e.offsetY)) return
205 | if (this.startTime) return
206 | this.startCallback?.(e)
207 | }
208 |
209 | /**
210 | * 单独加载某一张图片并计算其实际渲染宽高
211 | * @param { number } cellIndex 奖品索引
212 | * @param { number } imgIndex 奖品图片索引
213 | * @param { Function } callBack 图片加载完毕回调
214 | */
215 | private async loadAndCacheImg (
216 | cellName: 'blocks' | 'prizes' | 'buttons',
217 | cellIndex: number,
218 | imgName: 'blockImgs' | 'prizeImgs' | 'btnImgs',
219 | imgIndex: number,
220 | callBack: () => void
221 | ) {
222 | // 获取图片信息
223 | const cell: BlockType | PrizeType | ButtonType = this[cellName][cellIndex]
224 | if (!cell || !cell.imgs) return
225 | const imgInfo = cell.imgs[imgIndex]
226 | if (!imgInfo) return
227 | if (!this[imgName][cellIndex]) this[imgName][cellIndex] = []
228 | // 异步加载图片
229 | this.loadImg(imgInfo.src, imgInfo).then(res => {
230 | this[imgName][cellIndex][imgIndex] = res
231 | callBack.call(this)
232 | }).catch(err => {
233 | console.error(`${cellName}[${cellIndex}].imgs[${imgIndex}] ${err}`)
234 | })
235 | }
236 |
237 | /**
238 | * 计算图片的渲染宽高
239 | * @param imgObj 图片标签元素
240 | * @param imgInfo 图片信息
241 | * @param maxWidth 最大宽度
242 | * @param maxHeight 最大高度
243 | * @return [渲染宽度, 渲染高度]
244 | */
245 | private computedWidthAndHeight (
246 | imgObj: HTMLImageElement | UniImageType,
247 | imgInfo: ImgType,
248 | maxWidth: number,
249 | maxHeight: number
250 | ): [number, number] {
251 | // 根据配置的样式计算图片的真实宽高
252 | if (!imgInfo.width && !imgInfo.height) {
253 | // 如果没有配置宽高, 则使用图片本身的宽高
254 | return [imgObj.width, imgObj.height]
255 | } else if (imgInfo.width && !imgInfo.height) {
256 | // 如果只填写了宽度, 没填写高度
257 | let trueWidth = this.getWidth(imgInfo.width, maxWidth)
258 | // 那高度就随着宽度进行等比缩放
259 | return [trueWidth, imgObj.height * (trueWidth / imgObj.width)]
260 | } else if (!imgInfo.width && imgInfo.height) {
261 | // 如果只填写了宽度, 没填写高度
262 | let trueHeight = this.getHeight(imgInfo.height, maxHeight)
263 | // 那宽度就随着高度进行等比缩放
264 | return [imgObj.width * (trueHeight / imgObj.height), trueHeight]
265 | }
266 | // 如果宽度和高度都填写了, 就如实计算
267 | return [
268 | this.getWidth(imgInfo.width, maxWidth),
269 | this.getHeight(imgInfo.height, maxHeight)
270 | ]
271 | }
272 |
273 | /**
274 | * 开始绘制
275 | */
276 | protected draw (): void {
277 | const { config, ctx, _defaultConfig, _defaultStyle } = this
278 | // 触发绘制前回调
279 | config.beforeDraw?.call(this, ctx)
280 | // 清空画布
281 | ctx.clearRect(-this.Radius, -this.Radius, this.Radius * 2, this.Radius * 2)
282 | // 绘制blocks边框
283 | this.prizeRadius = this.blocks.reduce((radius, block, blockIndex) => {
284 | if (hasBackground(block.background)) {
285 | ctx.beginPath()
286 | ctx.fillStyle = block.background!
287 | ctx.arc(0, 0, radius, 0, Math.PI * 2, false)
288 | ctx.fill()
289 | }
290 | block.imgs && block.imgs.forEach((imgInfo, imgIndex) => {
291 | if (!this.blockImgs[blockIndex]) return
292 | const blockImg = this.blockImgs[blockIndex][imgIndex]
293 | if (!blockImg) return
294 | // 绘制图片
295 | const [trueWidth, trueHeight] = this.computedWidthAndHeight(blockImg, imgInfo, radius * 2, radius * 2)
296 | const [xAxis, yAxis] = [this.getOffsetX(trueWidth), this.getHeight(imgInfo.top, radius * 2) - radius]
297 | ctx.save()
298 | imgInfo.rotate && ctx.rotate(getAngle(this.rotateDeg))
299 | this.drawImage(blockImg, xAxis, yAxis, trueWidth, trueHeight)
300 | ctx.restore()
301 | })
302 | return radius - this.getLength(block.padding && block.padding.split(' ')[0])
303 | }, this.Radius)
304 | // 计算起始弧度
305 | this.prizeDeg = 360 / this.prizes.length
306 | this.prizeRadian = getAngle(this.prizeDeg)
307 | let start = getAngle(-90 + this.rotateDeg + _defaultConfig.offsetDegree)
308 | // 计算文字横坐标
309 | const getFontX = (line: string) => {
310 | return this.getOffsetX(ctx.measureText(line).width)
311 | }
312 | // 计算文字纵坐标
313 | const getFontY = (font: FontType, height: number, lineIndex: number) => {
314 | // 优先使用字体行高, 要么使用默认行高, 其次使用字体大小, 否则使用默认字体大小
315 | const lineHeight = font.lineHeight || _defaultStyle.lineHeight || font.fontSize || _defaultStyle.fontSize
316 | return this.getHeight(font.top, height) + (lineIndex + 1) * this.getLength(lineHeight)
317 | }
318 | ctx.save()
319 | // 绘制prizes奖品区域
320 | this.prizes.forEach((prize, prizeIndex) => {
321 | // 计算当前奖品区域中间坐标点
322 | let currMiddleDeg = start + prizeIndex * this.prizeRadian
323 | // 奖品区域可见高度
324 | let prizeHeight = this.prizeRadius - this.maxBtnRadius
325 | // 绘制背景
326 | const background = prize.background || _defaultStyle.background
327 | hasBackground(background) && drawSector(
328 | config.flag, ctx,
329 | this.maxBtnRadius, this.prizeRadius,
330 | currMiddleDeg - this.prizeRadian / 2,
331 | currMiddleDeg + this.prizeRadian / 2,
332 | this.getLength(_defaultConfig.gutter),
333 | background
334 | )
335 | // 计算临时坐标并旋转文字
336 | let x = Math.cos(currMiddleDeg) * this.prizeRadius
337 | let y = Math.sin(currMiddleDeg) * this.prizeRadius
338 | ctx.translate(x, y)
339 | ctx.rotate(currMiddleDeg + getAngle(90))
340 | // 绘制图片
341 | prize.imgs && prize.imgs.forEach((imgInfo, imgIndex) => {
342 | if (!this.prizeImgs[prizeIndex]) return
343 | const prizeImg = this.prizeImgs[prizeIndex][imgIndex]
344 | if (!prizeImg) return
345 | const [trueWidth, trueHeight] = this.computedWidthAndHeight(
346 | prizeImg,
347 | imgInfo,
348 | this.prizeRadian * this.prizeRadius,
349 | prizeHeight
350 | )
351 | const [xAxis, yAxis] = [this.getOffsetX(trueWidth), this.getHeight(imgInfo.top, prizeHeight)]
352 | this.drawImage(prizeImg, xAxis, yAxis, trueWidth, trueHeight)
353 | })
354 | // 逐行绘制文字
355 | prize.fonts && prize.fonts.forEach(font => {
356 | let fontColor = font.fontColor || _defaultStyle.fontColor
357 | let fontWeight = font.fontWeight || _defaultStyle.fontWeight
358 | let fontSize = this.getLength(font.fontSize || _defaultStyle.fontSize)
359 | let fontStyle = font.fontStyle || _defaultStyle.fontStyle
360 | ctx.fillStyle = fontColor!
361 | ctx.font = `${fontWeight} ${fontSize >> 0}px ${fontStyle}`
362 | let lines = [], text = String(font.text)
363 | if (Object.prototype.hasOwnProperty.call(font, 'wordWrap') ? font.wordWrap : _defaultStyle.wordWrap) {
364 | text = removeEnter(text)
365 | let str = ''
366 | for (let i = 0; i < text.length; i++) {
367 | str += text[i]
368 | let currWidth = ctx.measureText(str).width
369 | let maxWidth = (this.prizeRadius - getFontY(font, prizeHeight, lines.length))
370 | * Math.tan(this.prizeRadian / 2) * 2 - this.getLength(_defaultConfig.gutter)
371 | if (currWidth > this.getWidth(font.lengthLimit || _defaultStyle.lengthLimit, maxWidth)) {
372 | lines.push(str.slice(0, -1))
373 | str = text[i]
374 | }
375 | }
376 | if (str) lines.push(str)
377 | if (!lines.length) lines.push(text)
378 | } else {
379 | lines = text.split('\n')
380 | }
381 | lines.filter(line => !!line).forEach((line, lineIndex) => {
382 | ctx.fillText(line, getFontX(line), getFontY(font, prizeHeight, lineIndex))
383 | })
384 | })
385 | // 修正旋转角度和原点坐标
386 | ctx.rotate(getAngle(360) - currMiddleDeg - getAngle(90))
387 | ctx.translate(-x, -y)
388 | })
389 | ctx.restore()
390 | // 绘制按钮
391 | this.buttons.forEach((btn, btnIndex) => {
392 | let radius = this.getHeight(btn.radius)
393 | // 绘制背景颜色
394 | this.maxBtnRadius = Math.max(this.maxBtnRadius, radius)
395 | if (hasBackground(btn.background)) {
396 | ctx.beginPath()
397 | ctx.fillStyle = btn.background as string
398 | ctx.arc(0, 0, radius, 0, Math.PI * 2, false)
399 | ctx.fill()
400 | }
401 | // 绘制指针
402 | if (btn.pointer && hasBackground(btn.background)) {
403 | ctx.beginPath()
404 | ctx.fillStyle = btn.background as string
405 | ctx.moveTo(-radius, 0)
406 | ctx.lineTo(radius, 0)
407 | ctx.lineTo(0, -radius * 2)
408 | ctx.closePath()
409 | ctx.fill()
410 | }
411 | // 绘制按钮图片
412 | btn.imgs && btn.imgs.forEach((imgInfo, imgIndex) => {
413 | if (!this.btnImgs[btnIndex]) return
414 | const btnImg = this.btnImgs[btnIndex][imgIndex]
415 | if (!btnImg) return
416 | const [trueWidth, trueHeight] = this.computedWidthAndHeight(btnImg, imgInfo, radius * 2, radius * 2)
417 | const [xAxis, yAxis] = [this.getOffsetX(trueWidth), this.getHeight(imgInfo.top, radius)]
418 | this.drawImage(btnImg, xAxis, yAxis, trueWidth, trueHeight)
419 | })
420 | // 绘制按钮文字
421 | btn.fonts && btn.fonts.forEach(font => {
422 | let fontColor = font.fontColor || _defaultStyle.fontColor
423 | let fontWeight = font.fontWeight || _defaultStyle.fontWeight
424 | let fontSize = this.getLength(font.fontSize || _defaultStyle.fontSize)
425 | let fontStyle = font.fontStyle || _defaultStyle.fontStyle
426 | ctx.fillStyle = fontColor!
427 | ctx.font = `${fontWeight} ${fontSize >> 0}px ${fontStyle}`
428 | String(font.text).split('\n').forEach((line, lineIndex) => {
429 | ctx.fillText(line, getFontX(line), getFontY(font, radius, lineIndex))
430 | })
431 | })
432 | })
433 | // 触发绘制后回调
434 | config.afterDraw?.call(this, ctx)
435 | }
436 |
437 | /**
438 | * 对外暴露: 开始抽奖方法
439 | */
440 | public play (): void {
441 | // 再次拦截, 因为play是可以异步调用的
442 | if (this.startTime) return
443 | this.startTime = Date.now()
444 | this.prizeFlag = void 0
445 | this.run()
446 | }
447 |
448 | /**
449 | * 对外暴露: 缓慢停止方法
450 | * @param index 中奖索引
451 | */
452 | public stop (index: number): void {
453 | // 判断 prizeFlag 是否等于 -1
454 | this.prizeFlag = index < 0 ? -1 : index % this.prizes.length
455 | // 如果是 -1 就初始化状态
456 | if (this.prizeFlag === -1) {
457 | this.rotateDeg = this.prizeDeg / 2 - this._defaultConfig.offsetDegree
458 | this.draw()
459 | }
460 | }
461 |
462 | /**
463 | * 实际开始执行方法
464 | * @param num 记录帧动画执行多少次
465 | */
466 | private run (num: number = 0): void {
467 | const { rAF, prizeFlag, prizeDeg, rotateDeg, _defaultConfig } = this
468 | if (prizeFlag === -1) return (this.startTime = 0, void 0)
469 | let interval = Date.now() - this.startTime
470 | // 先完全旋转, 再停止
471 | if (interval >= _defaultConfig.accelerationTime && prizeFlag !== void 0) {
472 | // 记录帧率
473 | this.FPS = interval / num
474 | // 记录开始停止的时间戳
475 | this.endTime = Date.now()
476 | // 记录开始停止的位置
477 | this.stopDeg = rotateDeg
478 | // 停止范围
479 | const stopRange = (Math.random() * prizeDeg - prizeDeg / 2) * this.getLength(_defaultConfig.stopRange)
480 | // 测算最终停止的角度
481 | let i = 0
482 | while (++i) {
483 | const endDeg = 360 * i - prizeFlag * prizeDeg - rotateDeg - _defaultConfig.offsetDegree + stopRange
484 | let currSpeed = Tween[_defaultConfig.speedFunction].easeOut(this.FPS, this.stopDeg, endDeg, _defaultConfig.decelerationTime) - this.stopDeg
485 | if (currSpeed > _defaultConfig.speed) {
486 | this.endDeg = endDeg
487 | break
488 | }
489 | }
490 | return this.slowDown()
491 | }
492 | this.rotateDeg = (rotateDeg + Tween[_defaultConfig.speedFunction].easeIn(interval, 0, _defaultConfig.speed, _defaultConfig.accelerationTime)) % 360
493 | this.draw()
494 | rAF(this.run.bind(this, num + 1))
495 | }
496 |
497 | /**
498 | * 缓慢停止的方法
499 | */
500 | private slowDown (): void {
501 | const { rAF, prizes, prizeFlag, stopDeg, endDeg, _defaultConfig } = this
502 | let interval = Date.now() - this.endTime
503 | if (prizeFlag === -1) return (this.startTime = 0, void 0)
504 | if (interval >= _defaultConfig.decelerationTime) {
505 | this.startTime = 0
506 | this.endCallback?.({...prizes.find((prize, index) => index === prizeFlag)})
507 | return
508 | }
509 | this.rotateDeg = Tween[_defaultConfig.speedFunction].easeOut(interval, stopDeg, endDeg, _defaultConfig.decelerationTime) % 360
510 | this.draw()
511 | rAF(this.slowDown.bind(this))
512 | }
513 |
514 | /**
515 | * 获取相对宽度
516 | * @param length 将要转换的宽度
517 | * @param width 宽度计算百分比
518 | * @return 返回相对宽度
519 | */
520 | private getWidth (
521 | length: string | number | undefined,
522 | width = this.prizeRadian * this.prizeRadius
523 | ): number {
524 | if (isExpectType(length, 'number')) return (length as number)
525 | if (isExpectType(length, 'string')) return this.changeUnits(length as string, width)
526 | return 0
527 | }
528 |
529 | /**
530 | * 获取相对高度
531 | * @param length 将要转换的高度
532 | * @param height 高度计算百分比
533 | * @return 返回相对高度
534 | */
535 | private getHeight (
536 | length: string | number | undefined,
537 | height: number = this.prizeRadius
538 | ): number {
539 | if (isExpectType(length, 'number')) return (length as number)
540 | if (isExpectType(length, 'string')) return this.changeUnits(length as string, height)
541 | return 0
542 | }
543 |
544 | /**
545 | * 获取相对(居中)X坐标
546 | * @param width
547 | * @return 返回x坐标
548 | */
549 | private getOffsetX (width: number): number {
550 | return -width / 2
551 | }
552 |
553 | /**
554 | * 换算渲染坐标
555 | * @param x
556 | * @param y
557 | */
558 | protected conversionAxis (x: number, y: number): [number, number] {
559 | const { config } = this
560 | return [x / config.dpr - this.Radius, y / config.dpr - this.Radius]
561 | }
562 | }
563 |
--------------------------------------------------------------------------------
/src/observer/array.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 重写数组的原型方法
3 | */
4 | const oldArrayProto = Array.prototype
5 | const newArrayProto = Object.create(oldArrayProto)
6 | const methods = ['push', 'pop', 'shift', 'unshift', 'sort', 'splice', 'reverse']
7 | methods.forEach(method => {
8 | newArrayProto[method] = function (...args: any[]) {
9 | const res = oldArrayProto[method].apply(this, args)
10 | const luckyOb = this['__luckyOb__']
11 | if (['push', 'unshift', 'splice'].includes(method)) luckyOb.walk(this)
12 | luckyOb.dep.notify()
13 | return res
14 | }
15 | })
16 |
17 | export { newArrayProto }
18 |
--------------------------------------------------------------------------------
/src/observer/dep.ts:
--------------------------------------------------------------------------------
1 | import Watcher from './watcher'
2 |
3 | export default class Dep {
4 | static target: Watcher | null
5 | private subs: Array
6 |
7 | /**
8 | * 订阅中心构造器
9 | */
10 | constructor () {
11 | this.subs = []
12 | }
13 |
14 | /**
15 | * 收集依赖
16 | * @param {*} sub
17 | */
18 | public addSub (sub: Watcher) {
19 | // 此处临时使用includes防重复添加
20 | if (!this.subs.includes(sub)) {
21 | this.subs.push(sub)
22 | }
23 | }
24 |
25 | /**
26 | * 派发更新
27 | */
28 | public notify () {
29 | this.subs.forEach(sub => {
30 | sub.update()
31 | })
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/observer/index.ts:
--------------------------------------------------------------------------------
1 | import Dep from './dep'
2 | import { hasProto, def } from './utils'
3 | import { newArrayProto } from './array'
4 |
5 | export default class Observer {
6 | value: any
7 | dep: Dep
8 |
9 | /**
10 | * 观察者构造器
11 | * @param value
12 | */
13 | constructor (value: any) {
14 | // this.value = value
15 | this.dep = new Dep()
16 | // 将响应式对象代理到当前value上面, 并且将当前的enumerable设置为false
17 | def(value, '__luckyOb__', this)
18 | if (Array.isArray(value)) { // 如果是数组, 则重写原型方法
19 | if (hasProto) {
20 | value['__proto__'] = newArrayProto
21 | } else {
22 | Object.getOwnPropertyNames(newArrayProto).forEach(key => {
23 | def(value, key, newArrayProto[key])
24 | })
25 | }
26 | }
27 | this.walk(value)
28 | }
29 |
30 | walk (data: object | any[]) {
31 | Object.keys(data).forEach(key => {
32 | defineReactive(data, key, data[key])
33 | })
34 | }
35 | }
36 |
37 | /**
38 | * 处理响应式
39 | * @param { Object | Array } data
40 | */
41 | export function observe (data: any): Observer | void {
42 | if (!data || typeof data !== 'object') return
43 | let luckyOb: Observer | void
44 | if ('__luckyOb__' in data) {
45 | luckyOb = data['__luckyOb__']
46 | } else {
47 | luckyOb = new Observer(data)
48 | }
49 | return luckyOb
50 | }
51 |
52 | /**
53 | * 重写 setter / getter
54 | * @param {*} data
55 | * @param {*} key
56 | * @param {*} val
57 | */
58 | export function defineReactive (data: any, key: string | number, val: any) {
59 | const dep = new Dep()
60 | const property = Object.getOwnPropertyDescriptor(data, key)
61 | if (property && property.configurable === false) {
62 | return
63 | }
64 | const getter = property && property.get
65 | const setter = property && property.set
66 | if ((!getter || setter) && arguments.length === 2) {
67 | val = data[key]
68 | }
69 | let childOb = observe(val)
70 | Object.defineProperty(data, key, {
71 | get: () => {
72 | const value = getter ? getter.call(data) : val
73 | if (Dep.target) {
74 | dep.addSub(Dep.target)
75 | if (childOb) {
76 | childOb.dep.addSub(Dep.target)
77 | }
78 | }
79 | return value
80 | },
81 | set: (newVal) => {
82 | if (newVal === val) return
83 | val = newVal
84 | if (getter && !setter) return
85 | if (setter) {
86 | setter.call(data, newVal)
87 | } else {
88 | val = newVal
89 | }
90 | childOb = observe(newVal)
91 | dep.notify()
92 | }
93 | })
94 | }
95 |
--------------------------------------------------------------------------------
/src/observer/utils.ts:
--------------------------------------------------------------------------------
1 |
2 | import { isExpectType } from '../utils'
3 |
4 | export const hasProto = '__proto__' in {}
5 |
6 | export function def (obj: object, key: string | number, val: any, enumerable?: boolean) {
7 | Object.defineProperty(obj, key, {
8 | value: val,
9 | enumerable: !!enumerable,
10 | writable: true,
11 | configurable: true
12 | })
13 | }
14 |
15 | export function parsePath (path: string) {
16 | path += '.'
17 | let segments: string[] = [], segment = ''
18 | for (let i = 0; i < path.length; i++) {
19 | let curr = path[i]
20 | if (/\[|\./.test(curr)) {
21 | segments.push(segment)
22 | segment = ''
23 | } else if (/\W/.test(curr)) {
24 | continue
25 | } else {
26 | segment += curr
27 | }
28 | }
29 | return function (data: object | any[]) {
30 | return segments.reduce((data, key) => {
31 | return data[key]
32 | }, data)
33 | }
34 | }
35 |
36 | export function traverse (value: any) {
37 | // const seenObjects = new Set()
38 | const dfs = (data: any) => {
39 | if (!isExpectType(data, 'array', 'object')) return
40 | Object.keys(data).forEach(key => {
41 | const value = data[key]
42 | dfs(value)
43 | })
44 | }
45 | dfs(value)
46 | // seenObjects.clear()
47 | }
--------------------------------------------------------------------------------
/src/observer/watcher.ts:
--------------------------------------------------------------------------------
1 | import Lucky from '../lib/lucky'
2 | import Dep from './dep'
3 | import { parsePath, traverse } from './utils'
4 |
5 | export interface WatchOptType {
6 | handler?: () => Function
7 | immediate?: boolean
8 | deep?: boolean
9 | }
10 |
11 | let uid = 0
12 | export default class Watcher {
13 | id: number
14 | $lucky: Lucky
15 | expr: string | Function
16 | cb: Function
17 | deep: boolean
18 | getter: Function
19 | value: any
20 |
21 | /**
22 | * 观察者构造器
23 | * @param {*} $lucky
24 | * @param {*} expr
25 | * @param {*} cb
26 | */
27 | constructor ($lucky: Lucky, expr: string | Function, cb: Function, options: WatchOptType = {}) {
28 | this.id = uid++
29 | this.$lucky = $lucky
30 | this.expr = expr
31 | this.deep = !!options.deep
32 | if (typeof expr === 'function') {
33 | this.getter = expr
34 | } else {
35 | this.getter = parsePath(expr)
36 | }
37 | this.cb = cb
38 | this.value = this.get()
39 | }
40 |
41 | /**
42 | * 根据表达式获取新值
43 | */
44 | get () {
45 | Dep.target = this
46 | const value = this.getter.call(this.$lucky, this.$lucky)
47 | // 处理深度监听
48 | if (this.deep) {
49 | traverse(value)
50 | }
51 | Dep.target = null
52 | return value
53 | }
54 |
55 | /**
56 | * 触发 watcher 更新
57 | */
58 | update () {
59 | // get获取新值
60 | const newVal = this.get()
61 | // 读取之前存储的旧值
62 | const oldVal = this.value
63 | this.value = newVal
64 | // 触发 watch 回调
65 | this.cb.call(this.$lucky, newVal, oldVal)
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/types/card.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FontType,
3 | ImgType,
4 | BackgroundType
5 | } from './index'
6 |
7 | export interface MaskType {
8 | background?: BackgroundType
9 | fonts?: Array
10 | imgs?: Array
11 | }
12 |
13 | export interface WatermarkType {
14 | margin?: string
15 | rotate?: number
16 | opacity?: number
17 | text?: FontType['text']
18 | fontColor?: FontType['fontColor']
19 | fontSize?: FontType['fontSize']
20 | fontStyle?: FontType['fontStyle']
21 | fontWeight?: FontType['fontWeight']
22 | }
23 |
24 | export interface DefaultConfigType {
25 | percent?: string | number
26 | cleanZone?: {
27 | x: string | number
28 | y: string | number
29 | width: string | number
30 | height: string | number
31 | }
32 | }
33 |
34 | export type StartCallbackType = () => void
35 | export type EndCallbackType = () => void
36 |
37 | export default interface LuckyCardConfig {
38 | mask?: MaskType
39 | watermark?: WatermarkType
40 | start?: StartCallbackType
41 | end?: EndCallbackType
42 | }
43 |
--------------------------------------------------------------------------------
/src/types/grid.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FontType,
3 | ImgType,
4 | BorderRadiusType,
5 | BackgroundType,
6 | ShadowType
7 | } from './index'
8 |
9 | export interface PrizeFontType extends FontType {
10 | wordWrap?: boolean
11 | lengthLimit?: string | number
12 | }
13 |
14 | export interface ButtonFontType extends FontType {
15 | wordWrap?: boolean
16 | lengthLimit?: string | number
17 | }
18 |
19 | export type CellFontType = PrizeFontType | ButtonFontType
20 |
21 | export interface BlockImgType extends ImgType {}
22 |
23 | export interface PrizeImgType extends ImgType {
24 | activeSrc?: string
25 | }
26 |
27 | export interface ButtonImgType extends ImgType {}
28 |
29 | export type CellImgType = PrizeImgType | ButtonImgType
30 |
31 | export interface BlockType {
32 | borderRadius?: BorderRadiusType
33 | background?: BackgroundType
34 | padding?: string
35 | paddingTop?: string | number
36 | paddingRight?: string | number
37 | paddingBottom?: string | number
38 | paddingLeft?: string | number
39 | imgs?: Array
40 | }
41 |
42 | export interface CellType {
43 | x: number
44 | y: number
45 | col: number
46 | row: number
47 | borderRadius?: BorderRadiusType
48 | background: BackgroundType
49 | shadow?: ShadowType
50 | fonts?: Array
51 | imgs?: Array
52 | }
53 |
54 | export type PrizeType = CellType
55 |
56 | export type ButtonType = CellType & {
57 | callback?: Function
58 | }
59 |
60 | export interface DefaultConfigType {
61 | gutter?: number
62 | speed?: number
63 | accelerationTime?: number
64 | decelerationTime?: number
65 | }
66 |
67 | export interface DefaultStyleType {
68 | borderRadius?: BorderRadiusType
69 | background?: BackgroundType
70 | shadow?: ShadowType
71 | fontColor?: PrizeFontType['fontColor']
72 | fontSize?: PrizeFontType['fontSize']
73 | fontStyle?: PrizeFontType['fontStyle']
74 | fontWeight?: PrizeFontType['fontWeight']
75 | lineHeight?: PrizeFontType['lineHeight']
76 | wordWrap?: PrizeFontType['wordWrap']
77 | lengthLimit?: PrizeFontType['lengthLimit']
78 | }
79 |
80 | export interface ActiveStyleType {
81 | background?: BackgroundType
82 | shadow?: ShadowType
83 | fontColor?: PrizeFontType['fontColor']
84 | fontSize?: PrizeFontType['fontSize']
85 | fontStyle?: PrizeFontType['fontStyle']
86 | fontWeight?: PrizeFontType['fontWeight']
87 | lineHeight?: PrizeFontType['lineHeight']
88 | }
89 |
90 | export type RowsType = number
91 | export type ColsType = number
92 | export type StartCallbackType = (e: MouseEvent, button?: ButtonType) => void
93 | export type EndCallbackType = (prize: object) => void
94 |
95 | export default interface LuckyGridConfig {
96 | rows?: RowsType
97 | cols?: ColsType
98 | blocks?: Array
99 | prizes?: Array
100 | buttons?: Array
101 | button?: ButtonType
102 | defaultConfig?: DefaultConfigType
103 | defaultStyle?: DefaultStyleType
104 | activeStyle?: ActiveStyleType
105 | start?: StartCallbackType
106 | end?: EndCallbackType
107 | }
108 |
--------------------------------------------------------------------------------
/src/types/index.ts:
--------------------------------------------------------------------------------
1 | // 字体类型
2 | export interface FontType {
3 | text: string
4 | top?: string | number
5 | fontColor?: string
6 | fontSize?: string
7 | fontStyle?: string
8 | fontWeight?: string
9 | lineHeight?: string
10 | }
11 |
12 | // 图片类型
13 | export interface ImgType {
14 | src: string
15 | top?: string | number
16 | width?: string
17 | height?: string
18 | $resolve?: Function
19 | }
20 |
21 | export type BorderRadiusType = string | number
22 | export type BackgroundType = string
23 | export type ShadowType = string
24 |
25 | export interface ConfigType {
26 | // 临时处理元素类型, 当版本升到4.x之后就可以删掉了
27 | nodeType: number
28 | // 配置
29 | ob?: boolean
30 | // flag: 'WEB' | 'MINI-WX' | 'UNI-H5' | 'UNI-MINI-WX'
31 | flag: 'WEB' | 'MP-WX' | 'UNI-H5' | 'UNI-MP' | 'TARO-H5' | 'TARO-MP'
32 | el?: string
33 | divElement?: HTMLDivElement
34 | canvasElement?: HTMLCanvasElement
35 | ctx: CanvasRenderingContext2D
36 | dpr: number
37 | width: string
38 | height: string
39 | unitFunc?: (num: number, unit: string) => number
40 | // 覆盖方法
41 | rAF?: Function
42 | setTimeout: Function
43 | setInterval: Function
44 | clearTimeout: Function
45 | clearInterval: Function
46 | // 组件生命周期
47 | beforeCreate?: Function
48 | beforeInit?: Function
49 | afterInit?: Function
50 | beforeDraw?: Function
51 | afterDraw?: Function
52 | }
53 |
54 | export interface UniImageType {
55 | path: string
56 | width: number
57 | height: number
58 | }
59 |
--------------------------------------------------------------------------------
/src/types/wheel.ts:
--------------------------------------------------------------------------------
1 | import {
2 | FontType,
3 | ImgType,
4 | BackgroundType
5 | } from './index'
6 |
7 | export interface PrizeFontType extends FontType {
8 | wordWrap?: boolean
9 | lengthLimit?: string | number
10 | }
11 |
12 | export interface ButtonFontType extends FontType {}
13 |
14 | export interface BlockImgType extends ImgType {
15 | rotate?: boolean
16 | }
17 |
18 | export interface PrizeImgType extends ImgType {}
19 |
20 | export interface ButtonImgType extends ImgType {}
21 |
22 | export interface BlockType {
23 | padding?: string
24 | background?: BackgroundType
25 | imgs?: Array
26 | }
27 |
28 | export interface PrizeType {
29 | background?: BackgroundType
30 | fonts?: Array
31 | imgs?: Array
32 | }
33 |
34 | export interface ButtonType {
35 | radius?: string
36 | pointer?: boolean
37 | background?: BackgroundType
38 | fonts?: Array
39 | imgs?: Array
40 | }
41 |
42 | export interface DefaultConfigType {
43 | gutter?: string | number
44 | offsetDegree?: number
45 | speed?: number
46 | speedFunction?: string
47 | accelerationTime?: number
48 | decelerationTime?: number
49 | stopRange?: number
50 | }
51 |
52 | export interface DefaultStyleType {
53 | background?: BackgroundType
54 | fontColor?: PrizeFontType['fontColor']
55 | fontSize?: PrizeFontType['fontSize']
56 | fontStyle?: PrizeFontType['fontStyle']
57 | fontWeight?: PrizeFontType['fontWeight']
58 | lineHeight?: PrizeFontType['lineHeight']
59 | wordWrap?: PrizeFontType['wordWrap']
60 | lengthLimit?: PrizeFontType['lengthLimit']
61 | }
62 |
63 | export type StartCallbackType = (e: MouseEvent) => void
64 | export type EndCallbackType = (prize: object) => void
65 |
66 | export default interface LuckyWheelConfig {
67 | blocks?: Array
68 | prizes?: Array
69 | buttons?: Array
70 | defaultConfig?: DefaultConfigType
71 | defaultStyle?: DefaultStyleType
72 | start?: StartCallbackType
73 | end?: EndCallbackType
74 | }
75 |
--------------------------------------------------------------------------------
/src/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 判断是否是期望的类型
3 | * @param { any } param 将要判断的变量
4 | * @param { ...string } types 期望的类型
5 | * @return { boolean } 返回期望是否正确
6 | */
7 | export const isExpectType = (param: any, ...types: string[]): boolean => {
8 | return types.some(type => Object.prototype.toString.call(param).slice(8, -1).toLowerCase() === type)
9 | }
10 |
11 | /**
12 | * 移除\n
13 | * @param { string } str 将要处理的字符串
14 | * @return { string } 返回新的字符串
15 | */
16 | export const removeEnter = (str: string): string => {
17 | return [].filter.call(str, s => s !== '\n').join('')
18 | }
19 |
20 | /**
21 | * 把任何数据类型转成数字
22 | * @param num
23 | */
24 | export const getNumber = (num: any): number => {
25 | if (num === null) return 0
26 | if (typeof num === 'object') return NaN
27 | if (typeof num === 'number') return num
28 | if (typeof num === 'string') {
29 | if (num[num.length - 1] === '%') {
30 | return Number(num.slice(0, -1)) / 100
31 | }
32 | return Number(num)
33 | }
34 | return NaN
35 | }
36 |
37 | /**
38 | * 判断颜色是否有效 (透明色 === 无效)
39 | * @param color 颜色
40 | */
41 | export const hasBackground = (color: string | undefined | null): boolean => {
42 | if (typeof color !== 'string') return false
43 | color = color.toLocaleLowerCase().trim()
44 | if (color === 'transparent') return false
45 | if (/^rgba/.test(color)) {
46 | const alpha = /([^\s,]+)\)$/.exec(color)
47 | if (getNumber(alpha) === 0) return false
48 | }
49 | return true
50 | }
51 |
52 | /**
53 | * 通过padding计算
54 | * @return { object } block 边框信息
55 | */
56 | export const computePadding = (
57 | block: { padding?: string }
58 | ): [number, number, number, number] => {
59 | let padding = block.padding?.replace(/px/g, '').split(' ').map(n => ~~n) || [0],
60 | paddingTop = 0,
61 | paddingBottom = 0,
62 | paddingLeft = 0,
63 | paddingRight = 0
64 | switch (padding.length) {
65 | case 1:
66 | paddingTop = paddingBottom = paddingLeft = paddingRight = padding[0]
67 | break
68 | case 2:
69 | paddingTop = paddingBottom = padding[0]
70 | paddingLeft = paddingRight = padding[1]
71 | break
72 | case 3:
73 | paddingTop = padding[0]
74 | paddingLeft = paddingRight = padding[1]
75 | paddingBottom = padding[2]
76 | break
77 | default:
78 | paddingTop = padding[0]
79 | paddingBottom = padding[1]
80 | paddingLeft = padding[2]
81 | paddingRight = padding[3]
82 | }
83 | // 检查是否单独传入值, 并且不是0
84 | const res = { paddingTop, paddingBottom, paddingLeft, paddingRight }
85 | for (let key in res) {
86 | // 是否含有这个属性, 并且是数字或字符串
87 | res[key] = Object.prototype.hasOwnProperty.call(block, key) && isExpectType(block[key], 'string', 'number')
88 | ? ~~String(block[key]).replace(/px/g, '')
89 | : res[key]
90 | }
91 | return [paddingTop, paddingBottom, paddingLeft, paddingRight]
92 | }
93 |
--------------------------------------------------------------------------------
/src/utils/math.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 转换为运算角度
3 | * @param { number } deg 数学角度
4 | * @return { number } 运算角度
5 | */
6 | export const getAngle = (deg: number): number => {
7 | return Math.PI / 180 * deg
8 | }
9 |
10 | /**
11 | * 根据角度计算圆上的点
12 | * @param { number } deg 运算角度
13 | * @param { number } r 半径
14 | * @return { Array } 坐标[x, y]
15 | */
16 | export const getArcPointerByDeg = (deg: number, r: number): [number, number] => {
17 | return [+(Math.cos(deg) * r).toFixed(8), +(Math.sin(deg) * r).toFixed(8)]
18 | }
19 |
20 | /**
21 | * 根据点计算切线方程
22 | * @param { number } x 横坐标
23 | * @param { number } y 纵坐标
24 | * @return { Array } [斜率, 常数]
25 | */
26 | export const getTangentByPointer = (x: number, y: number): Array => {
27 | let k = - x / y
28 | let b = -k * x + y
29 | return [k, b]
30 | }
31 |
32 | // 根据三点画圆弧
33 | export const drawRadian = (
34 | flag: string,
35 | ctx: CanvasRenderingContext2D,
36 | r: number,
37 | start: number,
38 | end: number,
39 | direction: boolean = true
40 | ) => {
41 | // 如果角度大于等于180度, 则分两次绘制, 因为 arcTo 无法绘制180度的圆弧
42 | if (Math.abs(end - start).toFixed(8) >= getAngle(180).toFixed(8)) {
43 | let middle = (end + start) / 2
44 | if (direction) {
45 | drawRadian(flag, ctx, r, start, middle, direction)
46 | drawRadian(flag, ctx, r, middle, end, direction)
47 | } else {
48 | drawRadian(flag, ctx, r, middle, end, direction)
49 | drawRadian(flag, ctx, r, start, middle, direction)
50 | }
51 | return false
52 | }
53 | // 如果方法相反, 则交换起点和终点
54 | if (!direction) [start, end] = [end, start]
55 | const [x1, y1] = getArcPointerByDeg(start, r)
56 | const [x2, y2] = getArcPointerByDeg(end, r)
57 | const [k1, b1] = getTangentByPointer(x1, y1)
58 | const [k2, b2] = getTangentByPointer(x2, y2)
59 | // 计算两条切线的交点
60 | let x0 = (b2 - b1) / (k1 - k2)
61 | let y0 = (k2 * b1 - k1 * b2) / (k2 - k1)
62 | // 如果有任何一条切线垂直于x轴, 则斜率不存在
63 | if (isNaN(x0)) {
64 | Math.abs(x1) === +r.toFixed(8) && (x0 = x1)
65 | Math.abs(x2) === +r.toFixed(8) && (x0 = x2)
66 | }
67 | if (k1 === Infinity || k1 === -Infinity) {
68 | y0 = k2 * x0 + b2
69 | }
70 | else if (k2 === Infinity || k2 === -Infinity) {
71 | y0 = k1 * x0 + b1
72 | }
73 | ctx.lineTo(x1, y1)
74 | // 微信小程序下 arcTo 在安卓真机下绘制有 bug
75 | if (flag.indexOf('MP') > 0) {
76 | ctx.quadraticCurveTo(x0, y0, x2, y2)
77 | } else {
78 | ctx.arcTo(x0, y0, x2, y2, r)
79 | }
80 | }
81 |
82 | // 绘制扇形
83 | export const drawSector = (
84 | flag: string,
85 | ctx: CanvasRenderingContext2D,
86 | minRadius: number,
87 | maxRadius: number,
88 | start: number,
89 | end: number,
90 | gutter: number,
91 | background: string
92 | ) => {
93 | // 如果不存在 getter, 则直接使用 arc 绘制扇形
94 | if (!gutter) {
95 | ctx.beginPath()
96 | ctx.fillStyle = background
97 | ctx.moveTo(0, 0)
98 | ctx.arc(0, 0, maxRadius, start, end, false)
99 | ctx.closePath()
100 | ctx.fill()
101 | } else {
102 | drawSectorByArcTo(flag, ctx, minRadius, maxRadius, start, end, gutter, background)
103 | }
104 | }
105 |
106 | // 根据arcTo绘制扇形
107 | export const drawSectorByArcTo = (
108 | flag: string,
109 | ctx: CanvasRenderingContext2D,
110 | minRadius: number,
111 | maxRadius: number,
112 | start: number,
113 | end: number,
114 | gutter: number,
115 | background: string
116 | ) => {
117 | if (!minRadius) minRadius = gutter
118 | // 内外圆弧分别进行等边缩放
119 | let maxGutter = getAngle(90 / Math.PI / maxRadius * gutter)
120 | let minGutter = getAngle(90 / Math.PI / minRadius * gutter)
121 | let maxStart = start + maxGutter
122 | let maxEnd = end - maxGutter
123 | let minStart = start + minGutter
124 | let minEnd = end - minGutter
125 | ctx.beginPath()
126 | ctx.fillStyle = background
127 | ctx.moveTo(...getArcPointerByDeg(maxStart, maxRadius))
128 | drawRadian(flag, ctx, maxRadius, maxStart, maxEnd, true)
129 | // 如果 getter 比按钮短就绘制圆弧, 反之计算新的坐标点
130 | if (minEnd > minStart) {
131 | drawRadian(flag, ctx, minRadius, minStart, minEnd, false)
132 | } else {
133 | ctx.lineTo(
134 | ...getArcPointerByDeg(
135 | (start + end) / 2,
136 | gutter / 2 / Math.abs(Math.sin((start - end) / 2))
137 | )
138 | )
139 | }
140 | ctx.closePath()
141 | ctx.fill()
142 | }
143 |
144 | // 绘制圆角矩形 (由于微信小程序的 arcTo 有 bug, 下面的圆弧使用二次贝塞尔曲线代替)
145 | export const drawRoundRect = (
146 | ctx: CanvasRenderingContext2D,
147 | x: number,
148 | y: number,
149 | w: number,
150 | h: number,
151 | r: number,
152 | color: string
153 | ) => {
154 | let min = Math.min(w, h)
155 | if (r > min / 2) r = min / 2
156 | ctx.beginPath()
157 | ctx.fillStyle = color
158 | ctx.moveTo(x + r, y)
159 | ctx.lineTo(x + r, y)
160 | ctx.lineTo(x + w - r, y)
161 | // ctx.arcTo(x + w, y, x + w, y + r, r)
162 | ctx.quadraticCurveTo(x + w, y, x + w, y + r)
163 | ctx.lineTo(x + w, y + h - r)
164 | // ctx.arcTo(x + w, y + h, x + w - r, y + h, r)
165 | ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h)
166 | ctx.lineTo(x + r, y + h)
167 | // ctx.arcTo(x, y + h, x, y + h - r, r)
168 | ctx.quadraticCurveTo(x, y + h, x, y + h - r)
169 | ctx.lineTo(x, y + r)
170 | // ctx.arcTo(x, y, x + r, y, r)
171 | ctx.quadraticCurveTo(x, y, x + r, y)
172 | ctx.closePath()
173 | ctx.fill()
174 | }
175 |
176 | /**
177 | * 创建线性渐变色
178 | */
179 | export const getLinearGradient = (
180 | ctx: CanvasRenderingContext2D,
181 | x: number,
182 | y: number,
183 | w: number,
184 | h: number,
185 | background: string
186 | ) => {
187 | const context = (/linear-gradient\((.+)\)/.exec(background) as Array)[1]
188 | .split(',') // 根据逗号分割
189 | .map((text: string) => text.trim()) // 去除两边空格
190 | let deg = context.shift(), direction: [number, number, number, number] = [0, 0, 0, 0]
191 | // 通过起始点和角度计算渐变终点的坐标点, 这里感谢泽宇大神提醒我使用勾股定理....
192 | if (deg.includes('deg')) {
193 | deg = deg.slice(0, -3) % 360
194 | // 根据4个象限定义起点坐标, 根据45度划分8个区域计算终点坐标
195 | const getLenOfTanDeg = (deg: number) => Math.tan(deg / 180 * Math.PI)
196 | if (deg >= 0 && deg < 45) direction = [x, y + h, x + w, y + h - w * getLenOfTanDeg(deg - 0)]
197 | else if (deg >= 45 && deg < 90) direction = [x, y + h, (x + w) - h * getLenOfTanDeg(deg - 45), y]
198 | else if (deg >= 90 && deg < 135) direction = [x + w, y + h, (x + w) - h * getLenOfTanDeg(deg - 90), y]
199 | else if (deg >= 135 && deg < 180) direction = [x + w, y + h, x, y + w * getLenOfTanDeg(deg - 135)]
200 | else if (deg >= 180 && deg < 225) direction = [x + w, y, x, y + w * getLenOfTanDeg(deg - 180)]
201 | else if (deg >= 225 && deg < 270) direction = [x + w, y, x + h * getLenOfTanDeg(deg - 225), y + h]
202 | else if (deg >= 270 && deg < 315) direction = [x, y, x + h * getLenOfTanDeg(deg - 270), y + h]
203 | else if (deg >= 315 && deg < 360) direction = [x, y, x + w, y + h - w * getLenOfTanDeg(deg - 315)]
204 | }
205 | // 创建四个简单的方向坐标
206 | else if (deg.includes('top')) direction = [x, y + h, x, y]
207 | else if (deg.includes('bottom')) direction = [x, y, x, y + h]
208 | else if (deg.includes('left')) direction = [x + w, y, x, y]
209 | else if (deg.includes('right')) direction = [x, y, x + w, y]
210 | // 创建线性渐变必须使用整数坐标
211 | const gradient = ctx.createLinearGradient(...(direction.map(n => n >> 0) as [number, number, number, number]))
212 | // 这里后期重构, 先用any代替
213 | return context.reduce((gradient: any, item: any, index: any) => {
214 | const info = item.split(' ')
215 | if (info.length === 1) gradient.addColorStop(index, info[0])
216 | else if (info.length === 2) gradient.addColorStop(...info)
217 | return gradient
218 | }, gradient)
219 | }
220 |
--------------------------------------------------------------------------------
/src/utils/polyfill.js:
--------------------------------------------------------------------------------
1 | /**
2 | * 由于部分低版本下的某些 app 可能会缺少某些原型方法, 这里增加兼容
3 | */
4 |
5 | // vivo x7 下网易云游戏 app 缺少 includes 方法
6 | if (!String.prototype.includes) {
7 | String.prototype.includes = function(search, start) {
8 | 'use strict';
9 | if (typeof start !== 'number') {
10 | start = 0;
11 | }
12 | if (start + search.length > this.length) {
13 | return false;
14 | } else {
15 | return this.indexOf(search, start) !== -1;
16 | }
17 | };
18 | }
19 |
20 | // vivo x7 下网易云游戏 app 缺少 find 方法
21 | if (!Array.prototype.find) {
22 | Object.defineProperty(Array.prototype, 'find', {
23 | value: function(predicate) {
24 | // 1. Let O be ? ToObject(this value).
25 | if (this == null) {
26 | throw new TypeError('"this" is null or not defined');
27 | }
28 | var o = Object(this);
29 | // 2. Let len be ? ToLength(? Get(O, "length")).
30 | var len = o.length >>> 0;
31 | // 3. If IsCallable(predicate) is false, throw a TypeError exception.
32 | if (typeof predicate !== 'function') {
33 | throw new TypeError('predicate must be a function');
34 | }
35 | // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
36 | var thisArg = arguments[1];
37 | // 5. Let k be 0.
38 | var k = 0;
39 | // 6. Repeat, while k < len
40 | while (k < len) {
41 | // a. Let Pk be ! ToString(k).
42 | // b. Let kValue be ? Get(O, Pk).
43 | // c. Let testResult be ToBoolean(? Call(predicate, T, « kValue, k, O »)).
44 | // d. If testResult is true, return kValue.
45 | var kValue = o[k];
46 | if (predicate.call(thisArg, kValue, k, o)) {
47 | return kValue;
48 | }
49 | // e. Increase k by 1.
50 | k++;
51 | }
52 | // 7. Return undefined.
53 | return void 0;
54 | }
55 | });
56 | }
57 |
--------------------------------------------------------------------------------
/src/utils/tween.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * 缓动函数
3 | * t: current time(当前时间)
4 | * b: beginning value(初始值)
5 | * c: change in value(变化量)
6 | * d: duration(持续时间)
7 | *
8 | * 感谢张鑫旭大佬 https://github.com/zhangxinxu/Tween
9 | */
10 |
11 | interface SpeedType {
12 | easeIn: (t: number, b: number, c: number, d: number) => number
13 | easeOut: (t: number, b: number, c: number, d: number) => number
14 | }
15 |
16 | // 二次方的缓动
17 | export const quad: SpeedType = {
18 | easeIn: function (t, b, c, d) {
19 | if (t >= d) t = d
20 | return c * (t /= d) * t + b
21 | },
22 | easeOut: function (t, b, c, d) {
23 | if (t >= d) t = d
24 | return -c * (t /= d) * (t - 2) + b
25 | }
26 | }
27 |
28 | // 三次方的缓动
29 | export const cubic: SpeedType = {
30 | easeIn: function (t, b, c, d) {
31 | if (t >= d) t = d
32 | return c * (t /= d) * t * t + b
33 | },
34 | easeOut: function (t, b, c, d) {
35 | if (t >= d) t = d
36 | return c * ((t = t / d - 1) * t * t + 1) + b
37 | }
38 | }
39 |
40 | // 四次方的缓动
41 | export const quart: SpeedType = {
42 | easeIn: function (t, b, c, d) {
43 | if (t >= d) t = d
44 | return c * (t /= d) * t * t * t + b
45 | },
46 | easeOut: function (t, b, c, d) {
47 | if (t >= d) t = d
48 | return -c * ((t = t / d - 1) * t * t * t - 1) + b
49 | }
50 | }
51 |
52 | // 五次方的缓动
53 | export const quint: SpeedType = {
54 | easeIn: function (t, b, c, d) {
55 | if (t >= d) t = d
56 | return c * (t /= d) * t * t * t * t + b
57 | },
58 | easeOut: function (t, b, c, d) {
59 | if (t >= d) t = d
60 | return c * ((t = t / d - 1) * t * t * t * t + 1) + b
61 | }
62 | }
63 |
64 | // 正弦曲线的缓动
65 | export const sine: SpeedType = {
66 | easeIn: function (t, b, c, d) {
67 | if (t >= d) t = d
68 | return -c * Math.cos(t / d * (Math.PI / 2)) + c + b
69 | },
70 | easeOut: function (t, b, c, d) {
71 | if (t >= d) t = d
72 | return c * Math.sin(t / d * (Math.PI / 2)) + b
73 | }
74 | }
75 |
76 | // 指数曲线的缓动
77 | export const expo: SpeedType = {
78 | easeIn: function (t, b, c, d) {
79 | if (t >= d) t = d
80 | return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b
81 | },
82 | easeOut: function (t, b, c, d) {
83 | if (t >= d) t = d
84 | return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b
85 | }
86 | }
87 |
88 | // 圆形曲线的缓动
89 | export const circ: SpeedType = {
90 | easeIn: function (t, b, c, d) {
91 | if (t >= d) t = d
92 | return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b
93 | },
94 | easeOut: function (t, b, c, d) {
95 | if (t >= d) t = d
96 | return c * Math.sqrt(1 - (t = t / d - 1) * t) + b
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "allowJs": true,
6 | "strict": true,
7 | "importHelpers": true,
8 | "moduleResolution": "node",
9 | "skipLibCheck": true,
10 | "esModuleInterop": true,
11 | "allowSyntheticDefaultImports": true,
12 | "suppressImplicitAnyIndexErrors": true,
13 | "resolveJsonModule": true,
14 | "sourceMap": true,
15 | "lib": [
16 | "es2015",
17 | "es2016",
18 | "es2017",
19 | "esnext",
20 | "dom",
21 | "dom.iterable",
22 | "scripthost"
23 | ]
24 | },
25 | "exclude": [
26 | "node_modules/**"
27 | ],
28 | "include": [
29 | "src/**/*"
30 | ]
31 | }
32 |
--------------------------------------------------------------------------------
/umd.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e((t="undefined"!=typeof globalThis?globalThis:t||self).LuckyCanvas={})}(this,(function(t){"use strict";
2 | /*! *****************************************************************************
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission to use, copy, modify, and/or distribute this software for any
6 | purpose with or without fee is hereby granted.
7 |
8 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
9 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
10 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
11 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
12 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
13 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
14 | PERFORMANCE OF THIS SOFTWARE.
15 | ***************************************************************************** */var e=function(t,i){return(e=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])})(t,i)};function i(t,i){if("function"!=typeof i&&null!==i)throw new TypeError("Class extends value "+String(i)+" is not a constructor or null");function n(){this.constructor=t}e(t,i),t.prototype=null===i?Object.create(i):(n.prototype=i.prototype,new n)}var n=function(){return(n=Object.assign||function(t){for(var e,i=1,n=arguments.length;i0&&r[r.length-1])||6!==o[0]&&2!==o[0])){s=0;continue}if(3===o[0]&&(!r||o[1]>r[0]&&o[1]this.length)&&-1!==this.indexOf(t,e)}),Array.prototype.find||Object.defineProperty(Array.prototype,"find",{value:function(t){if(null==this)throw new TypeError('"this" is null or not defined');var e=Object(this),i=e.length>>>0;if("function"!=typeof t)throw new TypeError("predicate must be a function");for(var n=arguments[1],r=0;r '"+e.src+"' 不能为空或不合法"),"WEB"===n.config.flag){var s=new Image;s.src=t,s.onload=function(){return r(s)},s.onerror=function(){return o("=> '"+e.src+"' 图片加载失败")}}else e[i]=r}))},t.prototype.drawImage=function(t,e,i,n,r){var o,s=this.config,a=this.ctx;return["WEB","MP-WX"].includes(s.flag)?o=t:["UNI-H5","UNI-MP","TARO-H5","TARO-MP"].includes(s.flag)&&(o=t.path),a.drawImage(o,e,i,n,r)},t.prototype.getLength=function(t){return a(t,"number")?t:a(t,"string")?this.changeUnits(t):0},t.prototype.changeUnits=function(t,e){var i=this;return void 0===e&&(e=1),Number(t.replace(/^([-]*[0-9.]*)([a-z%]*)$/,(function(t,n,r){var o={"%":function(t){return t*(e/100)},px:function(t){return 1*t},rem:function(t){return t*i.htmlFontSize}}[r];if(o)return o(n);var s=i.config.unitFunc;return s?s(n,r):n})))},t.prototype.$set=function(t,e,i){t&&"object"==typeof t&&v(t,e,i)},t.prototype.$computed=function(t,e,i){var n=this;Object.defineProperty(t,e,{get:function(){return i.call(n)}})},t.prototype.$watch=function(t,e,i){void 0===i&&(i={}),"object"==typeof e&&(e=(i=e).handler);var n=new y(this,t,e,i);return i.immediate&&e.call(this,n.value),function(){}},t}(),x=function(t){return Math.PI/180*t},z=function(t,e){return[+(Math.cos(t)*e).toFixed(8),+(Math.sin(t)*e).toFixed(8)]},I=function(t,e){var i=-t/e;return[i,-i*t+e]},k=function(t,e,i,n,r,o){var s;if(void 0===o&&(o=!0),Math.abs(r-n).toFixed(8)>=x(180).toFixed(8)){var a=(r+n)/2;return o?(k(t,e,i,n,a,o),k(t,e,i,a,r,o)):(k(t,e,i,a,r,o),k(t,e,i,n,a,o)),!1}o||(n=(s=[r,n])[0],r=s[1]);var u=z(n,i),h=u[0],c=u[1],l=z(r,i),f=l[0],d=l[1],p=I(h,c),g=p[0],m=p[1],v=I(f,d),b=v[0],y=v[1],w=(y-m)/(g-b),S=(b*m-g*y)/(b-g);isNaN(w)&&(Math.abs(h)===+i.toFixed(8)&&(w=h),Math.abs(f)===+i.toFixed(8)&&(w=f)),g===1/0||g===-1/0?S=b*w+y:b!==1/0&&b!==-1/0||(S=g*w+m),e.lineTo(h,c),t.indexOf("MP")>0?e.quadraticCurveTo(w,S,f,d):e.arcTo(w,S,f,d,i)},S=function(t,e,i,n,r,o,s,a){i||(i=s);var u=x(90/Math.PI/n*s),h=x(90/Math.PI/i*s),c=r+u,l=o-u,f=r+h,d=o-h;e.beginPath(),e.fillStyle=a,e.moveTo.apply(e,z(c,n)),k(t,e,n,c,l,!0),d>f?k(t,e,i,f,d,!1):e.lineTo.apply(e,z((r+o)/2,s/2/Math.abs(Math.sin((r-o)/2)))),e.closePath(),e.fill()},T=function(t,e,i,n,r,o,s){var a=Math.min(n,r);o>a/2&&(o=a/2),t.beginPath(),t.fillStyle=s,t.moveTo(e+o,i),t.lineTo(e+o,i),t.lineTo(e+n-o,i),t.quadraticCurveTo(e+n,i,e+n,i+o),t.lineTo(e+n,i+r-o),t.quadraticCurveTo(e+n,i+r,e+n-o,i+r),t.lineTo(e+o,i+r),t.quadraticCurveTo(e,i+r,e,i+r-o),t.lineTo(e,i+o),t.quadraticCurveTo(e,i,e+o,i),t.closePath(),t.fill()},C={easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*(t/=n)*(t-2)+e}},W={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*Math.cos(t/n*(Math.PI/2))+i+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sin(t/n*(Math.PI/2))+e}},O={easeIn:function(t,e,i,n){return t>=n&&(t=n),0==t?e:i*Math.pow(2,10*(t/n-1))+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),t==n?e+i:i*(1-Math.pow(2,-10*t/n))+e}},_={easeIn:function(t,e,i,n){return t>=n&&(t=n),-i*(Math.sqrt(1-(t/=n)*t)-1)+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*Math.sqrt(1-(t=t/n-1)*t)+e}},E=Object.freeze({__proto__:null,quad:C,cubic:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t+1)+e}},quart:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),-i*((t=t/n-1)*t*t*t-1)+e}},quint:{easeIn:function(t,e,i,n){return t>=n&&(t=n),i*(t/=n)*t*t*t*t+e},easeOut:function(t,e,i,n){return t>=n&&(t=n),i*((t=t/n-1)*t*t*t*t+1)+e}},sine:W,expo:O,circ:_}),P=function(t){function e(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;return r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},r.defaultStyle={},r._defaultStyle={fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},r.Radius=0,r.prizeRadius=0,r.prizeDeg=0,r.prizeRadian=0,r.rotateDeg=0,r.maxBtnRadius=0,r.startTime=0,r.endTime=0,r.stopDeg=0,r.endDeg=0,r.FPS=16.6,r.blockImgs=[[]],r.prizeImgs=[[]],r.btnImgs=[[]],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:r.buttons.map((function(t){return t.imgs}))}),r}return i(e,t),e.prototype.initData=function(t){this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},e.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){return n({gutter:"0px",offsetDegree:0,speed:20,speedFunction:"quad",accelerationTime:2500,decelerationTime:2500,stopRange:.8},t.defaultConfig)})),this.$computed(this,"_defaultStyle",(function(){return n({fontSize:"18px",fontColor:"#000",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)}))},e.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){return t.init({btnImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},e.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this.config,s=this.ctx;this.Radius=Math.min(this.boxWidth,this.boxHeight)/2,null===(i=o.beforeInit)||void 0===i||i.call(this),s.translate(this.Radius,this.Radius),this.draw(),this.draw(),Object.keys(e).forEach((function(t){var i=t,n={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i],o=e[i];o&&o.forEach((function(t,e){t&&t.forEach((function(t,o){r.loadAndCacheImg(n,e,i,o,(function(){r.draw()}))}))}))})),null===(n=o.afterInit)||void 0===n||n.call(this)},e.prototype.handleClick=function(t){var e,i=this.ctx;i.beginPath(),i.arc(0,0,this.maxBtnRadius,0,2*Math.PI,!1),i.isPointInPath(t.offsetX,t.offsetY)&&(this.startTime||null===(e=this.startCallback)||void 0===e||e.call(this,t))},e.prototype.loadAndCacheImg=function(t,e,i,n,s){return r(this,void 0,void 0,(function(){var r,a,u=this;return o(this,(function(o){return(r=this[t][e])&&r.imgs&&(a=r.imgs[n])?(this[i][e]||(this[i][e]=[]),this.loadImg(a.src,a).then((function(t){u[i][e][n]=t,s.call(u)})).catch((function(i){console.error(t+"["+e+"].imgs["+n+"] "+i)})),[2]):[2]}))}))},e.prototype.computedWidthAndHeight=function(t,e,i,n){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var r=this.getWidth(e.width,i);return[r,t.height*(r/t.width)]}if(!e.width&&e.height){var o=this.getHeight(e.height,n);return[t.width*(o/t.height),o]}return[this.getWidth(e.width,i),this.getHeight(e.height,n)]},e.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,o=n.ctx,s=n._defaultConfig,a=n._defaultStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,o),o.clearRect(-this.Radius,-this.Radius,2*this.Radius,2*this.Radius),this.prizeRadius=this.blocks.reduce((function(t,e,n){return h(e.background)&&(o.beginPath(),o.fillStyle=e.background,o.arc(0,0,t,0,2*Math.PI,!1),o.fill()),e.imgs&&e.imgs.forEach((function(e,r){if(i.blockImgs[n]){var s=i.blockImgs[n][r];if(s){var a=i.computedWidthAndHeight(s,e,2*t,2*t),u=a[0],h=a[1],c=[i.getOffsetX(u),i.getHeight(e.top,2*t)-t],l=c[0],f=c[1];o.save(),e.rotate&&o.rotate(x(i.rotateDeg)),i.drawImage(s,l,f,u,h),o.restore()}}})),t-i.getLength(e.padding&&e.padding.split(" ")[0])}),this.Radius),this.prizeDeg=360/this.prizes.length,this.prizeRadian=x(this.prizeDeg);var c=x(-90+this.rotateDeg+s.offsetDegree),l=function(t){return i.getOffsetX(o.measureText(t).width)},f=function(t,e,n){var r=t.lineHeight||a.lineHeight||t.fontSize||a.fontSize;return i.getHeight(t.top,e)+(n+1)*i.getLength(r)};o.save(),this.prizes.forEach((function(t,e){var n=c+e*i.prizeRadian,d=i.prizeRadius-i.maxBtnRadius,p=t.background||a.background;h(p)&&function(t,e,i,n,r,o,s,a){s?S(t,e,i,n,r,o,s,a):(e.beginPath(),e.fillStyle=a,e.moveTo(0,0),e.arc(0,0,n,r,o,!1),e.closePath(),e.fill())}(r.flag,o,i.maxBtnRadius,i.prizeRadius,n-i.prizeRadian/2,n+i.prizeRadian/2,i.getLength(s.gutter),p);var g=Math.cos(n)*i.prizeRadius,m=Math.sin(n)*i.prizeRadius;o.translate(g,m),o.rotate(n+x(90)),t.imgs&&t.imgs.forEach((function(t,n){if(i.prizeImgs[e]){var r=i.prizeImgs[e][n];if(r){var o=i.computedWidthAndHeight(r,t,i.prizeRadian*i.prizeRadius,d),s=o[0],a=o[1],u=[i.getOffsetX(s),i.getHeight(t.top,d)],h=u[0],c=u[1];i.drawImage(r,h,c,s,a)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||a.fontColor,n=t.fontWeight||a.fontWeight,r=i.getLength(t.fontSize||a.fontSize),h=t.fontStyle||a.fontStyle;o.fillStyle=e,o.font=n+" "+(r>>0)+"px "+h;var c=[],p=String(t.text);if(Object.prototype.hasOwnProperty.call(t,"wordWrap")?t.wordWrap:a.wordWrap){p=u(p);for(var g="",m=0;m i.getWidth(t.lengthLimit||a.lengthLimit,b)&&(c.push(g.slice(0,-1)),g=p[m])}g&&c.push(g),c.length||c.push(p)}else c=p.split("\n");c.filter((function(t){return!!t})).forEach((function(e,i){o.fillText(e,l(e),f(t,d,i))}))})),o.rotate(x(360)-n-x(90)),o.translate(-g,-m)})),o.restore(),this.buttons.forEach((function(t,e){var n=i.getHeight(t.radius);i.maxBtnRadius=Math.max(i.maxBtnRadius,n),h(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.arc(0,0,n,0,2*Math.PI,!1),o.fill()),t.pointer&&h(t.background)&&(o.beginPath(),o.fillStyle=t.background,o.moveTo(-n,0),o.lineTo(n,0),o.lineTo(0,2*-n),o.closePath(),o.fill()),t.imgs&&t.imgs.forEach((function(t,r){if(i.btnImgs[e]){var o=i.btnImgs[e][r];if(o){var s=i.computedWidthAndHeight(o,t,2*n,2*n),a=s[0],u=s[1],h=[i.getOffsetX(a),i.getHeight(t.top,n)],c=h[0],l=h[1];i.drawImage(o,c,l,a,u)}}})),t.fonts&&t.fonts.forEach((function(t){var e=t.fontColor||a.fontColor,r=t.fontWeight||a.fontWeight,s=i.getLength(t.fontSize||a.fontSize),u=t.fontStyle||a.fontStyle;o.fillStyle=e,o.font=r+" "+(s>>0)+"px "+u,String(t.text).split("\n").forEach((function(e,i){o.fillText(e,l(e),f(t,n,i))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,o)},e.prototype.play=function(){this.startTime||(this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},e.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.rotateDeg=this.prizeDeg/2-this._defaultConfig.offsetDegree,this.draw())},e.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.prizeFlag,r=e.prizeDeg,o=e.rotateDeg,s=e._defaultConfig;if(-1!==n){var a=Date.now()-this.startTime;if(a>=s.accelerationTime&&void 0!==n){this.FPS=a/t,this.endTime=Date.now(),this.stopDeg=o;for(var u=(Math.random()*r-r/2)*this.getLength(s.stopRange),h=0;++h;){var c=360*h-n*r-o-s.offsetDegree+u;if(E[s.speedFunction].easeOut(this.FPS,this.stopDeg,c,s.decelerationTime)-this.stopDeg>s.speed){this.endDeg=c;break}}return this.slowDown()}this.rotateDeg=(o+E[s.speedFunction].easeIn(a,0,s.speed,s.accelerationTime))%360,this.draw(),i(this.run.bind(this,t+1))}else this.startTime=0},e.prototype.slowDown=function(){var t,e=this,i=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopDeg,a=e.endDeg,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>=u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,n({},r.find((function(t,e){return e===o})))));this.rotateDeg=E[u.speedFunction].easeOut(h,s,a,u.decelerationTime)%360,this.draw(),i(this.slowDown.bind(this))}else this.startTime=0},e.prototype.getWidth=function(t,e){return void 0===e&&(e=this.prizeRadian*this.prizeRadius),a(t,"number")?t:a(t,"string")?this.changeUnits(t,e):0},e.prototype.getHeight=function(t,e){return void 0===e&&(e=this.prizeRadius),a(t,"number")?t:a(t,"string")?this.changeUnits(t,e):0},e.prototype.getOffsetX=function(t){return-t/2},e.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr-this.Radius,e/i.dpr-this.Radius]},e}(w),D=function(t){function e(e,i){var n;void 0===i&&(i={});var r=t.call(this,e)||this;r.rows=3,r.cols=3,r.blocks=[],r.prizes=[],r.buttons=[],r.defaultConfig={},r._defaultConfig={gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},r.defaultStyle={},r._defaultStyle={borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",lineHeight:"",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},r.activeStyle={},r._activeStyle={background:"#ffce98",shadow:"",fontStyle:"",fontWeight:"",fontSize:"",lineHeight:"",fontColor:""},r.cellWidth=0,r.cellHeight=0,r.startTime=0,r.endTime=0,r.currIndex=0,r.stopIndex=0,r.endIndex=0,r.demo=!1,r.timer=0,r.FPS=16.6,r.prizeFlag=-1,r.cells=[],r.blockImgs=[[]],r.btnImgs=[[]],r.prizeImgs=[],e.ob&&(r.initData(i),r.initWatch()),r.initComputed(),null===(n=e.beforeCreate)||void 0===n||n.call(r);var o=r.buttons.map((function(t){return t.imgs}));return r.button&&o.push(r.button.imgs),r.init({blockImgs:r.blocks.map((function(t){return t.imgs})),prizeImgs:r.prizes.map((function(t){return t.imgs})),btnImgs:o}),r}return i(e,t),e.prototype.initData=function(t){this.$set(this,"rows",Number(t.rows)||3),this.$set(this,"cols",Number(t.cols)||3),this.$set(this,"blocks",t.blocks||[]),this.$set(this,"prizes",t.prizes||[]),this.$set(this,"buttons",t.buttons||[]),this.$set(this,"button",t.button),this.$set(this,"defaultConfig",t.defaultConfig||{}),this.$set(this,"defaultStyle",t.defaultStyle||{}),this.$set(this,"activeStyle",t.activeStyle||{}),this.$set(this,"startCallback",t.start),this.$set(this,"endCallback",t.end)},e.prototype.initComputed=function(){var t=this;this.$computed(this,"_defaultConfig",(function(){var e=n({gutter:5,speed:20,accelerationTime:2500,decelerationTime:2500},t.defaultConfig);return e.gutter=t.getLength(e.gutter),e.speed=e.speed/40,e})),this.$computed(this,"_defaultStyle",(function(){return n({borderRadius:20,fontColor:"#000",fontSize:"18px",fontStyle:"sans-serif",fontWeight:"400",background:"rgba(0,0,0,0)",shadow:"",wordWrap:!0,lengthLimit:"90%"},t.defaultStyle)})),this.$computed(this,"_activeStyle",(function(){return n({background:"#ffce98",shadow:""},t.activeStyle)}))},e.prototype.initWatch=function(){var t=this;this.$watch("blocks",(function(e){return t.init({blockImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("prizes",(function(e){return t.init({prizeImgs:e.map((function(t){return t.imgs}))})}),{deep:!0}),this.$watch("buttons",(function(e){var i=e.map((function(t){return t.imgs}));return t.button&&i.push(t.button.imgs),t.init({btnImgs:i})}),{deep:!0}),this.$watch("button",(function(){var e=t.buttons.map((function(t){return t.imgs}));return t.button&&e.push(t.button.imgs),t.init({btnImgs:e})}),{deep:!0}),this.$watch("rows",(function(){return t.init({})})),this.$watch("cols",(function(){return t.init({})})),this.$watch("defaultConfig",(function(){return t.draw()}),{deep:!0}),this.$watch("defaultStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("activeStyle",(function(){return t.draw()}),{deep:!0}),this.$watch("startCallback",(function(){return t.init({})})),this.$watch("endCallback",(function(){return t.init({})}))},e.prototype.init=function(e){var i,n,r=this;t.prototype.init.call(this);var o=this,s=o.config;o.ctx,o.button,null===(i=s.beforeInit)||void 0===i||i.call(this),this.draw(),Object.keys(e).forEach((function(t){var i=t,n=e[i],o={blockImgs:"blocks",prizeImgs:"prizes",btnImgs:"buttons"}[i];n&&n.forEach((function(t,e){t&&t.forEach((function(t,n){r.loadAndCacheImg(o,e,i,n,(function(){r.draw()}))}))}))})),null===(n=s.afterInit)||void 0===n||n.call(this)},e.prototype.handleClick=function(t){var e=this,i=this.ctx;s(s([],this.buttons),[this.button]).forEach((function(n){var r;if(n){var o=e.getGeometricProperty([n.x,n.y,n.col||1,n.row||1]),s=o[0],a=o[1],u=o[2],h=o[3];i.beginPath(),i.rect(s,a,u,h),i.isPointInPath(t.offsetX,t.offsetY)&&(e.startTime||("function"==typeof n.callback&&n.callback.call(e,n),null===(r=e.startCallback)||void 0===r||r.call(e,t,n)))}}))},e.prototype.loadAndCacheImg=function(t,e,i,n,s){return r(this,void 0,void 0,(function(){var r,a,u,h=this;return o(this,(function(o){return r=this[t][e],"buttons"===t&&!this.buttons.length&&this.button&&(r=this.button),r&&r.imgs&&(a=r.imgs[n])?(this[i][e]||(this[i][e]=[]),u=[this.loadImg(a.src,a),a.activeSrc&&this.loadImg(a.activeSrc,a,"$activeResolve")],Promise.all(u).then((function(t){var r=t[0],o=t[1];h[i][e][n]={defaultImg:r,activeImg:o},s.call(h)})).catch((function(i){console.error(t+"["+e+"].imgs["+n+"] "+i)})),[2]):[2]}))}))},e.prototype.computedWidthAndHeight=function(t,e,i){if(!e.width&&!e.height)return[t.width,t.height];if(e.width&&!e.height){var n=this.getWidth(e.width,i.col);return[n,t.height*(n/t.width)]}if(!e.width&&e.height){var r=this.getHeight(e.height,i.row);return[t.width*(r/t.height),r]}return[this.getWidth(e.width,i.col),this.getHeight(e.height,i.row)]},e.prototype.draw=function(){var t,e,i=this,n=this,r=n.config,o=n.ctx,c=n._defaultConfig,l=n._defaultStyle,f=n._activeStyle;null===(t=r.beforeDraw)||void 0===t||t.call(this,o),o.clearRect(0,0,this.boxWidth,this.boxHeight),this.cells=s(s([],this.prizes),this.buttons),this.button&&this.cells.push(this.button),this.cells.forEach((function(t){t.col=t.col||1,t.row=t.row||1})),this.prizeArea=this.blocks.reduce((function(t,e){var n=t.x,r=t.y,s=t.w,u=t.h,c=function(t){var e,i=(null===(e=t.padding)||void 0===e?void 0:e.replace(/px/g,"").split(" ").map((function(t){return~~t})))||[0],n=0,r=0,o=0,s=0;switch(i.length){case 1:n=r=o=s=i[0];break;case 2:n=r=i[0],o=s=i[1];break;case 3:n=i[0],o=s=i[1],r=i[2];break;default:n=i[0],r=i[1],o=i[2],s=i[3]}var u={paddingTop:n,paddingBottom:r,paddingLeft:o,paddingRight:s};for(var h in u)u[h]=Object.prototype.hasOwnProperty.call(t,h)&&a(t[h],"string","number")?~~String(t[h]).replace(/px/g,""):u[h];return[n,r,o,s]}(e),f=c[0],d=c[1],p=c[2],g=c[3],m=e.borderRadius?i.getLength(e.borderRadius):0,v=e.background||l.background;return h(v)&&T(o,n,r,s,u,m,i.handleBackground(n,r,s,u,v)),{x:n+p,y:r+f,w:s-p-g,h:u-f-d}}),{x:0,y:0,w:this.boxWidth,h:this.boxHeight}),this.cellWidth=(this.prizeArea.w-c.gutter*(this.cols-1))/this.cols,this.cellHeight=(this.prizeArea.h-c.gutter*(this.rows-1))/this.rows,this.cells.forEach((function(t,e){var n=i.getGeometricProperty([t.x,t.y,t.col,t.row]),s=n[0],a=n[1],c=n[2],d=n[3],p=!1;(void 0===i.prizeFlag||i.prizeFlag>-1)&&(p=e===i.currIndex%i.prizes.length>>0);var g=p?f.background:t.background||l.background;if(h(g)){var m=(p?f.shadow:t.shadow||l.shadow).replace(/px/g,"").split(",")[0].split(" ").map((function(t,e){return e<3?Number(t):t}));4===m.length&&(o.shadowColor=m[3],o.shadowOffsetX=m[0]*r.dpr,o.shadowOffsetY=m[1]*r.dpr,o.shadowBlur=m[2],m[0]>0?c-=m[0]:(c+=m[0],s-=m[0]),m[1]>0?d-=m[1]:(d+=m[1],a-=m[1])),T(o,s,a,c,d,i.getLength(t.borderRadius?t.borderRadius:l.borderRadius),i.handleBackground(s,a,c,d,g)),o.shadowColor="rgba(0, 0, 0, 0)",o.shadowOffsetX=0,o.shadowOffsetY=0,o.shadowBlur=0}var v="prizeImgs";e>=i.prizes.length&&(v="btnImgs",e-=i.prizes.length),t.imgs&&t.imgs.forEach((function(n,r){if(i[v][e]){var o=i[v][e][r];if(o){var u=p&&o.activeImg||o.defaultImg;if(u){var h=i.computedWidthAndHeight(u,n,t),c=h[0],l=h[1],f=[s+i.getOffsetX(c,t.col),a+i.getHeight(n.top,t.row)],d=f[0],g=f[1];i.drawImage(u,d,g,c,l)}}}})),t.fonts&&t.fonts.forEach((function(e){var n=p&&f.fontStyle?f.fontStyle:e.fontStyle||l.fontStyle,r=p&&f.fontWeight?f.fontWeight:e.fontWeight||l.fontWeight,h=p&&f.fontSize?i.getLength(f.fontSize):i.getLength(e.fontSize||l.fontSize),c=p&&f.lineHeight?f.lineHeight:e.lineHeight||l.lineHeight||e.fontSize||l.fontSize;o.font=r+" "+(h>>0)+"px "+n,o.fillStyle=p&&f.fontColor?f.fontColor:e.fontColor||l.fontColor;var d=[],g=String(e.text);if(Object.prototype.hasOwnProperty.call(e,"wordWrap")?e.wordWrap:l.wordWrap){g=u(g);for(var m="",v=0;vi.getWidth(e.lengthLimit||l.lengthLimit,t.col)&&(d.push(m.slice(0,-1)),m=g[v])}m&&d.push(m),d.length||d.push(g)}else d=g.split("\n");d.forEach((function(n,r){o.fillText(n,s+i.getOffsetX(o.measureText(n).width,t.col),a+i.getHeight(e.top,t.row)+(r+1)*i.getLength(c))}))}))})),null===(e=r.afterDraw)||void 0===e||e.call(this,o)},e.prototype.handleBackground=function(t,e,i,n,r){var o=this.ctx;return r.includes("linear-gradient")&&(r=function(t,e,i,n,r,o){var s=/linear-gradient\((.+)\)/.exec(o)[1].split(",").map((function(t){return t.trim()})),a=s.shift(),u=[0,0,0,0];if(a.includes("deg")){var h=function(t){return Math.tan(t/180*Math.PI)};(a=a.slice(0,-3)%360)>=0&&a<45?u=[e,i+r,e+n,i+r-n*h(a-0)]:a>=45&&a<90?u=[e,i+r,e+n-r*h(a-45),i]:a>=90&&a<135?u=[e+n,i+r,e+n-r*h(a-90),i]:a>=135&&a<180?u=[e+n,i+r,e,i+n*h(a-135)]:a>=180&&a<225?u=[e+n,i,e,i+n*h(a-180)]:a>=225&&a<270?u=[e+n,i,e+r*h(a-225),i+r]:a>=270&&a<315?u=[e,i,e+r*h(a-270),i+r]:a>=315&&a<360&&(u=[e,i,e+n,i+r-n*h(a-315)])}else a.includes("top")?u=[e,i+r,e,i]:a.includes("bottom")?u=[e,i,e,i+r]:a.includes("left")?u=[e+n,i,e,i]:a.includes("right")&&(u=[e,i,e+n,i]);var c=t.createLinearGradient.apply(t,u.map((function(t){return t>>0})));return s.reduce((function(t,e,i){var n=e.split(" ");return 1===n.length?t.addColorStop(i,n[0]):2===n.length&&t.addColorStop.apply(t,n),t}),c)}(o,t,e,i,n,r)),r},e.prototype.play=function(){var t=this.config.clearInterval;this.startTime||(t(this.timer),this.startTime=Date.now(),this.prizeFlag=void 0,this.run())},e.prototype.stop=function(t){this.prizeFlag=t<0?-1:t%this.prizes.length,-1===this.prizeFlag&&(this.currIndex=0,this.draw())},e.prototype.run=function(t){void 0===t&&(t=0);var e=this,i=e.rAF,n=e.currIndex,r=e.prizes,o=e.prizeFlag,s=e.startTime,a=e._defaultConfig,u=Date.now()-s;if(u>=a.accelerationTime&&void 0!==o){this.FPS=u/t,this.endTime=Date.now(),this.stopIndex=n;for(var h=0;++h;){var c=r.length*h+o-(n>>0);if(C.easeOut(this.FPS,this.stopIndex,c,a.decelerationTime)-this.stopIndex>a.speed){this.endIndex=c;break}}return this.slowDown()}this.currIndex=(n+C.easeIn(u,.1,a.speed,a.accelerationTime))%r.length,this.draw(),i(this.run.bind(this,t+1))},e.prototype.slowDown=function(){var t,e=this,i=e.rAF,r=e.prizes,o=e.prizeFlag,s=e.stopIndex,a=e.endIndex,u=e._defaultConfig,h=Date.now()-this.endTime;if(-1!==o){if(h>u.decelerationTime)return this.startTime=0,void(null===(t=this.endCallback)||void 0===t||t.call(this,n({},r.find((function(t,e){return e===o})))));this.currIndex=C.easeOut(h,s,a,u.decelerationTime)%r.length,this.draw(),i(this.slowDown.bind(this))}else this.startTime=0},e.prototype.walk=function(){var t=this,e=this.config,i=e.setInterval;(0,e.clearInterval)(this.timer),this.timer=i((function(){t.currIndex+=1,t.draw()}),1300)},e.prototype.getGeometricProperty=function(t){var e=t[0],i=t[1],n=t[2],r=t[3],o=this.cellWidth,s=this.cellHeight,a=this._defaultConfig.gutter,u=[this.prizeArea.x+(o+a)*e,this.prizeArea.y+(s+a)*i];return n&&r&&u.push(o*n+a*(n-1),s*r+a*(r-1)),u},e.prototype.getWidth=function(t,e){return void 0===e&&(e=1),a(t,"number")?t:a(t,"string")?this.changeUnits(t,this.cellWidth*e+this._defaultConfig.gutter*(e-1)):0},e.prototype.getHeight=function(t,e){return void 0===e&&(e=1),a(t,"number")?t:a(t,"string")?this.changeUnits(t,this.cellHeight*e+this._defaultConfig.gutter*(e-1)):0},e.prototype.getOffsetX=function(t,e){return void 0===e&&(e=1),(this.cellWidth*e+this._defaultConfig.gutter*(e-1)-t)/2},e.prototype.conversionAxis=function(t,e){var i=this.config;return[t/i.dpr,e/i.dpr]},e}(w);t.LuckyGrid=D,t.LuckyWheel=P,Object.defineProperty(t,"__esModule",{value:!0})}));
16 |
--------------------------------------------------------------------------------
/wx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/LuckDraw/lucky-canvas-invalid/950ff673375bd399871a2183a34f4c9c5b37ad65/wx.jpg
--------------------------------------------------------------------------------