├── .gitattributes
├── .gitignore
├── README.md
├── app.asar
├── asar_pack.BAT
├── bin
├── css
│ ├── index.css
│ └── wave-doing.css
├── img
│ ├── ICON.ico
│ ├── ICON.png
│ ├── cpu.svg
│ ├── error.svg
│ ├── file.svg
│ ├── loading.svg
│ ├── ok.svg
│ └── remove.svg
├── index.html
├── lib
│ ├── animate.css
│ ├── jquery.min.js
│ └── sine-waves.js
├── main.js
├── package.json
├── start.js
└── vendor.bundle.js
├── components
└── main-list.vue
├── index.js
├── limitPNG.js
├── move.bat
├── package.json
├── report.js
├── webpack.config.js
└── website
├── ICON.ico
├── assets
├── css
│ ├── admin.css
│ ├── amazeui.css
│ ├── amazeui.flat.css
│ ├── amazeui.flat.min.css
│ ├── amazeui.min.css
│ └── app.css
├── fonts
│ ├── FontAwesome.otf
│ ├── fontawesome-webfont.eot
│ ├── fontawesome-webfont.svg
│ ├── fontawesome-webfont.ttf
│ ├── fontawesome-webfont.woff
│ └── fontawesome-webfont.woff2
├── i
│ ├── app-icon72x72@2x.png
│ ├── examples
│ │ ├── admin-chrome.png
│ │ ├── admin-firefox.png
│ │ ├── admin-ie.png
│ │ ├── admin-opera.png
│ │ ├── admin-safari.png
│ │ ├── adminPage.png
│ │ ├── blogPage.png
│ │ ├── landing.png
│ │ ├── landingPage.png
│ │ ├── loginPage.png
│ │ └── sidebarPage.png
│ ├── favicon.png
│ └── startup-640x1096.png
└── js
│ ├── amazeui.ie8polyfill.js
│ ├── amazeui.ie8polyfill.min.js
│ ├── amazeui.js
│ ├── amazeui.min.js
│ ├── amazeui.widgets.helper.js
│ ├── amazeui.widgets.helper.min.js
│ ├── app.js
│ ├── handlebars.min.js
│ └── jquery.min.js
├── css.css
├── img
├── 1_lim[lossy-low].png
├── cpu.svg
├── gif3.gif
├── logo-mini.png
├── logo.png
├── lossy.svg
├── no1.svg
├── option.svg
├── timecus.png
├── top.svg
└── vs
│ ├── PNG_lossy_vs.png
│ ├── download.url
│ ├── lossy.png
│ ├── ss.png
│ └── vuelogo.png
├── index.css
├── index.html
├── lib
├── animate.css
├── jquery.event.move.js
├── jquery.twentytwenty.js
└── twentytwenty.css
└── rrr.html
/.gitattributes:
--------------------------------------------------------------------------------
1 |
2 | *.vue linguist-language=JavaScript
3 |
4 | *.css linguist-language=JavaScript
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # =========================
3 | # 忽略难以上传的文件
4 | # =========================
5 | test
6 | UI
7 | release
8 | electron-v1.2.0-win32-x64
9 | electron-v1.2.2-win32-x64
10 | .idea
11 | node_modules
12 |
13 | # Windows image file caches
14 | Thumbs.db
15 | ehthumbs.db
16 |
17 | # Folder config file
18 | Desktop.ini
19 |
20 | # Recycle Bin used on file shares
21 | $RECYCLE.BIN/
22 |
23 | # Windows Installer files
24 | *.cab
25 | *.msi
26 | *.msm
27 | *.msp
28 |
29 | # Windows shortcuts
30 | *.lnk
31 |
32 | # =========================
33 | # Operating System Files
34 | # =========================
35 |
36 | # OSX
37 | # =========================
38 |
39 | .DS_Store
40 | .AppleDouble
41 | .LSOverride
42 |
43 | # Thumbnails
44 | ._*
45 |
46 | # Files that might appear on external disk
47 | .Spotlight-V100
48 | .Trashes
49 |
50 | # Directories potentially created on remote AFP share
51 | .AppleDB
52 | .AppleDesktop
53 | Network Trash Folder
54 | Temporary Items
55 | .apdisk
56 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # limitPNG
2 |
3 |
4 |
5 |
6 | GUI PNG image compression tool ( GUI use Electron)
7 |
8 | [http://nullice.com/limitPNG/](http://nullice.com/limitPNG/)
9 |
10 | 这是个用 electron 开发的图形界面 PNG 图片压缩工具。
11 |
12 | 支持无损压缩和有损压缩。
13 |
14 | 比同类的 PNGGauntle 、 scriptPNG 、 Leanify 、 Caesium 能压缩得更小。
15 |
16 |
17 | ----
18 |
19 |
20 |
21 |
22 |
23 |
24 | 使用以下工具:
25 |
26 | 实际上这是一个压缩工具集合,使用了以下压缩工具的二进制文件,
27 |
28 | compression Using tool binary list :
29 | - [zopflipng](https://github.com/google/zopfli)
30 | - [pngwolf](http://bjoern.hoehrmann.de/pngwolf/)
31 | - [pngquant](https://pngquant.org/)
32 | - [TruePNG](http://x128.ho.ua/pngutils.html)
33 | - [PNGOUT](http://advsys.net/ken/utils.htm#pngout)
34 | - [ect](http://css-ig.net/)
35 |
36 | ----
37 | ### LICENSES
38 | **CC0 Public Domain**
39 |
40 | 这是一个共有领域作品,本人不保有此作品任何权益,任何人可以随意使用、修改和分发,不要求署名
41 |
42 | [](http://creativecommons.org/publicdomain/zero/1.0)
43 |
44 |
45 |
46 | -----------
47 |
48 | ##### 不显眼的功能:
49 |
50 | 
51 |
52 | 连续双击文件列表信息文字这里可以生成一个 html 格式的处理报告,有并在浏览器中打开,要保存请保存网页。
53 | 报告里面有详细的压缩信息,和使用压缩工具的流程日志:
54 |
55 | 
56 |
57 |
58 |
59 |
60 | 
61 |
62 | 右键窗口标题可以打开文件对比功能,选择一系列文件,会以第一个文件为原文件来计算大小,生成文件大小比较报告:
63 | 
64 |
65 | 
66 |
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/app.asar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/app.asar
--------------------------------------------------------------------------------
/asar_pack.BAT:
--------------------------------------------------------------------------------
1 | asar pack "E:\Work\GitHub\limitPNG\bin" app.asar
2 |
3 | pause
--------------------------------------------------------------------------------
/bin/css/index.css:
--------------------------------------------------------------------------------
1 |
2 | button.do {
3 | display: block;
4 | cursor: pointer;
5 | outline: none;
6 | position: absolute;
7 | left: 0;
8 | right: 0;
9 | margin: auto;
10 | transition: all .3s cubic-bezier(0.74, 0.07, 0.53, 1.06);
11 | background: linear-gradient(70deg, #1ad7f9, #5eece4);
12 | width: 130px;
13 | border-radius: 50px;
14 | color: #fff;
15 | font-size: 16px;
16 | font-weight: lighter;
17 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif;
18 | padding: 5px;
19 | border: none;
20 | }
21 |
22 | button.do:hover {
23 | transition: all .3s ease;
24 | margin-top: -2px;
25 | background: linear-gradient(20deg, #20cdff, #2dfff3);
26 |
27 | width: 135px;
28 | /* padding: 8px; */
29 | box-shadow: 0 10px 20px #54f3ff;
30 | /* border-radius: 0px; */
31 | }
32 |
33 | button.do:active {
34 | width: 140px;
35 | /* height: 30px; */
36 | transition: all .1s cubic-bezier(0.74, 0.07, 0.53, 1.06);
37 | font-size: 14px;
38 | }
39 |
40 | /* 必需 */
41 | .anime-transition {
42 | transition: all .4s ease;
43 | overflow: hidden;
44 | }
45 |
46 | .anime-enter, .anime-leave {
47 | height: 0;
48 | opacity: 0;
49 | }
50 |
51 | body {
52 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif;
53 | margin: 0;
54 | height: 100%;
55 | width: 100%;
56 | position: absolute;
57 | overflow: hidden;
58 |
59 | }
60 |
61 | .item div:not(.anime) {
62 | display: inline-block;
63 | padding: 6px 18px;
64 | margin: 4px 8px;
65 | cursor: default;
66 | user-select: none;
67 |
68 | background-color: rgba(255, 255, 255, 0);
69 | color: rgba(64, 181, 202, 0.72);
70 | font-weight: lighter;
71 | -webkit-user-select: none;
72 | }
73 |
74 | .item div:not(.anime).name {
75 | margin-left: 0;
76 | padding-left: 36px;
77 | white-space: nowrap;
78 | overflow: hidden;
79 | text-overflow: ellipsis;
80 | }
81 |
82 |
83 |
84 | .item div.done_ico img, .item div.doing_ico img, .item div.error_ico img {
85 | width: 16px;
86 | height: 16px;
87 | position: absolute;
88 | top: 0;
89 | bottom: 0;
90 | margin: auto;
91 | }
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 | .item div.doing_ico img {
100 | width: 14px;
101 | height: 14px;
102 | animation: fa-spin .65s infinite linear;
103 | }
104 | .item.anime-transition.error div {
105 | color: #ff8282;
106 | }
107 | @keyframes fa-spin {
108 | 0% {
109 | transform: rotate(0deg);
110 | }
111 |
112 | 100% {
113 | transform: rotate(360deg);
114 | }
115 | }
116 |
117 | .item div.done_ico, .item div.doing_ico ,.item div.error_ico{
118 | padding: 0px;
119 | position: absolute;
120 | top: 0;
121 | bottom: 0;
122 | margin: auto;
123 | margin-left: 12px;
124 | }
125 |
126 | #list {
127 | position: absolute;
128 | width: 100%;
129 | top: 64px;
130 | bottom: 75px;
131 | overflow: overlay;
132 |
133 | /*-webkit-user-select: none;*/
134 | }
135 |
136 |
137 |
138 | .item:first-child {
139 | margin-top: 8px;
140 | }
141 |
142 | .list_shadow_bottm {
143 | position: absolute;
144 | height: 20px;
145 | width: 100%;
146 | background: linear-gradient(0deg, #ffffff, rgba(255, 255, 255, 0));
147 | z-index: 999;
148 | bottom: 74px;
149 | }
150 |
151 | .list_shadow_top {
152 | position: absolute;
153 | height: 10px;
154 | width: 100%;
155 | background: linear-gradient(180deg, #ffffff, rgba(255, 255, 255, 0));
156 | z-index: 100;
157 | top: 63px;
158 | }
159 |
160 | .item div.name {
161 | margin-right: 220px;
162 | padding-left: 26px;
163 | display: block;
164 | position: relative;
165 | }
166 |
167 | .item div.wave_box {
168 | position: absolute;
169 | top: 20px;
170 | left: 50%;
171 | }
172 |
173 | .item div.size_old {
174 | position: absolute;
175 | right: 0;
176 | width: 85px;
177 | text-align: end;
178 | font-size: 14px;
179 | top: 0px;
180 | bottom: 0px;
181 | vertical-align: middle;
182 | padding: 2px 10px;
183 | margin: 10px 3px;
184 | }
185 |
186 |
187 | .cant_do{
188 | opacity: .5;
189 | pointer-events: none;
190 | }
191 |
192 | .size_new {
193 |
194 | position: absolute;
195 | right: 90px;
196 | width: 130px;
197 | text-align: end;
198 | font-size: 14px;
199 | margin: 10px 10px;
200 | top: 0px;
201 | bottom: 0px;
202 | vertical-align: middle;
203 | padding: 2px 10px;
204 | white-space: nowrap;
205 | }
206 |
207 | .item {
208 | position: relative;
209 | margin-bottom: 0;
210 | background-color: rgba(221, 253, 236, 0);
211 | border-bottom: 1px solid rgba(85, 193, 224, 0.16);
212 | }
213 |
214 | #drag_file_showbox {
215 | background-color: rgba(0, 130, 101, 0.15);
216 | position: fixed;
217 | top: 2px;
218 | left: 2px;
219 | right: 2px;
220 | bottom: 2px;
221 | border: 2px dashed #68C1AE;
222 | display: none;
223 | }
224 |
225 | /*@-webkit-keyframes gogogo {*/
226 | /*0% {*/
227 |
228 | /*-webkit-transform: rotate(0deg);*/
229 | /*border: 5px solid red;*/
230 |
231 | /*}*/
232 | /*50% {*/
233 | /*-webkit-transform: rotate(180deg);*/
234 | /*background: black;*/
235 | /*border: 5px solid yellow;*/
236 | /*}*/
237 | /*100% {*/
238 | /*-webkit-transform: rotate(360deg);*/
239 | /*background: white;*/
240 | /*border: 5px solid red;*/
241 | /*}*/
242 |
243 | /*}*/
244 |
245 | select {
246 | position: absolute;
247 | font-size: 12px;
248 | margin-top: 2px;
249 | font-family: "Microsoft YaHei","Hiragino Sans GB","ヒラギノ角ゴ ProN W3 Regular","WenQuanYi Micro Hei","Hiragino Kaku Gothic ProN","メイリオ",Helvetica,sans-serif;
250 | font-weight: lighter;
251 | padding: 4px 5px;
252 | border-radius: 4px;
253 | border: 1px solid rgba(158, 217, 224, 0.59);
254 | color: #4ab5c1;
255 | width: 75px;
256 | outline: none;
257 | cursor: pointer;
258 | opacity: .8;
259 | }
260 |
261 | select.lift {
262 | margin-left: 2px;
263 | }
264 |
265 | select.right {
266 | right: 0px;
267 | width: 90px;
268 | margin-right: 2px;
269 | }
270 |
271 | select:hover {
272 | border: 1px solid #9ed9e0;
273 | opacity: 1;
274 | }
275 |
276 | optgroup {
277 | color: #6ba0af;
278 | }
279 |
280 | .op_p {
281 | color: #3e8088;
282 | font-size: 13px;
283 | }
284 |
285 | option.op_limit {
286 | color: #00b8d4;
287 | }
288 |
289 | option.op_high {
290 | color: #23c6e4;
291 | }
292 |
293 | option.op_quick {
294 | color: #41b9c5;
295 | }
296 |
297 | .bottom_bar {
298 | position: absolute;
299 | width: 95%;
300 | bottom: 46px;
301 | left: 0;
302 | margin: auto;
303 | right: 0;
304 | }
305 |
306 | .item div.size_new {
307 | padding-right: 0;
308 | font-weight: normal;
309 | }
310 |
311 |
312 |
313 | /* 设置滚动条的样式 */
314 | ::-webkit-scrollbar {
315 | width: 10px;
316 | transition: all 1s;
317 | }
318 |
319 |
320 | /* 滚动槽 */
321 | ::-webkit-scrollbar-track {
322 | border-radius: 10px;
323 | }
324 |
325 | /* 滚动条滑块 */
326 | ::-webkit-scrollbar-thumb {
327 | border-radius: 10px;
328 | width: 11px;
329 | background: rgba(35, 205, 234, 0.23);
330 | border: 3px solid #fff;
331 | }
332 |
333 | ::-webkit-scrollbar-thumb:hover {
334 | transition: all 1s;
335 | border: none;
336 | }
337 |
338 | ::-webkit-scrollbar-thumb:hover {
339 |
340 | background: rgba(35, 205, 234, 0.33);
341 | }
342 |
343 | .loading_input_file img.icon{
344 | width: 26px;
345 | height: 26px;
346 | animation: fa-spin .55s infinite;
347 | margin-bottom: -6px;
348 | position: relative;
349 | }
350 |
351 | .loading_input_file {
352 | position: fixed;
353 | width: 100%;
354 | height: 100%;
355 | background-color: rgba(255, 255, 255, 0.76);
356 | z-index: 9999;
357 | }
358 |
359 | .info {
360 | position: absolute;
361 | top: 33%;
362 | margin: auto;
363 | width: 70%;
364 | left: 0;
365 | right: 0;
366 | vertical-align: middle;
367 | text-align: center;
368 | color: #3b96b7;
369 | }
370 | .thumbnail.anime {
371 | z-index: -1;
372 | top: 3px;
373 | padding: 0;
374 | width: 26px;
375 | height:15px;
376 | overflow: hidden;
377 | margin: 0;
378 | display: inline-block;
379 | opacity: .8;
380 | cursor:pointer;
381 |
382 | }
383 |
384 | .thumbnail.anime:hover{
385 |
386 | opacity: 1;
387 | box-shadow: 0 3px 5px #40e3e8;
388 | }
389 |
390 | .thumbnail.anime img {
391 | max-height: 30px;
392 | max-width: 40px;
393 | position: relative;
394 | margin-top: -50%;
395 | margin-left: 0;
396 |
397 | }
398 |
399 | .item div.size_new span {
400 | font-size: 10px;
401 | background: linear-gradient(30deg, rgba(46, 207, 236, 0.69), rgba(22, 214, 203, 0.94));
402 | color: rgb(255, 255, 255);
403 | padding: 2px 7px;
404 | border-radius: 9px;
405 | }
406 |
407 |
408 | .menu_bar button img {
409 | width: 13px;
410 | height: 13px;
411 |
412 | }
413 |
414 | #remove_all img
415 | {
416 | width: 14px;
417 | height: 14px;
418 | margin-top: 2px;
419 | }
420 |
421 |
422 |
423 | #thread_number img
424 | {
425 | width: 16px;
426 | height: 16px;
427 | margin-top: 2px;
428 |
429 | }
430 |
431 | button#thread_number {
432 | opacity: .55;
433 | position: relative;
434 | }
435 |
436 | button#thread_number:hover {
437 | opacity: 1;
438 | }
439 |
440 | .menu_bar button,.window_nar button {
441 | background-color: rgba(255, 255, 255, 0);
442 | border: 1px solid rgba(255, 255, 255, 0);
443 | margin: 4px 0;
444 | outline: none;
445 | border-radius: 2px;
446 | cursor: pointer;
447 | opacity: .6;
448 |
449 |
450 | }
451 |
452 |
453 | .menu_bar button:hover, .window_nar button:hover{
454 | opacity: 1;
455 | border: 1px solid #a0e1e2;
456 | }
457 |
458 | .menu_bar {
459 | position: absolute;
460 | width: 100%;
461 | background-color: white;
462 | top: 30px;
463 | z-index: 222;
464 |
465 | }
466 |
467 | #open_file{
468 | margin-left: 10px;
469 | }
470 |
471 | .list_info {
472 | position: absolute;
473 | display: inline-block;
474 | right: 26px;
475 | font-size: 12px;
476 | color: #93d3da;
477 | margin-top: 7px;
478 | }
479 |
480 | div#vue_app {
481 | border-radius: 4px;
482 | background: #fff;
483 | box-shadow: 0 3px 7px rgba(21, 41, 43, 0.43);
484 | /*padding: 0px 10px;*/
485 | position: absolute;
486 | top: 2px;
487 | bottom: 10px;
488 | left: 10px;
489 | right: 10px;
490 |
491 | }
492 |
493 | ::selection {
494 | background: rgba(126, 255, 247, 0.32);
495 | color: #93D3DA;
496 | border-radius: 3px;
497 | }
498 |
499 |
500 |
501 | div#drag_box {
502 | position: absolute;
503 | width: 100%;
504 | height: 100%;
505 | background: rebeccapurple;
506 | }
507 |
508 | #vue_app div{ -webkit-user-select: none;}
509 |
510 | #list,button,select,.menu_bar{ -webkit-app-region: no-drag;}
511 |
512 | #vue_app div.list_info{
513 | -webkit-user-select: text;
514 | }
515 |
516 | .title {
517 | font-size: 11px;
518 | width: 84px;
519 | padding: 6px 12px;
520 | color: rgba(0, 156, 179, 0.58);
521 | cursor: pointer;
522 | white-space: nowrap;
523 | -webkit-user-select: none;
524 | -webkit-app-region: no-drag;
525 | }
526 |
527 | .title span.ver {
528 | font-size: 9px;
529 | color: rgba(49, 67, 70, 0.45);
530 | }
531 |
532 | .title:hover
533 | {
534 | animation-name: jello;
535 | transform-origin: 40% 20% ;
536 | animation-duration: .35s;
537 | animation-fill-mode: both;
538 | }
539 |
540 | select#select_thread {
541 | position: absolute;
542 | width: 28px;
543 | height: 20px;
544 | display: inline-block;
545 | top: 2px;
546 | left: 0px;
547 | opacity: 0;
548 | }
549 |
550 | .window_nar {
551 | position: absolute;
552 | right: 10px;
553 | z-index: 555;
554 | top: 0px;
555 | }
556 |
557 |
558 | .window_nar button {
559 | color: #2caebd;
560 | width: 30px;
561 | background-color: #fff;
562 | border: 1px solid rgba(158, 217, 224, 0.0);
563 | }
564 |
565 | div#welcome {
566 | position: absolute;
567 | left: 0;
568 | right: 0;
569 | margin: auto;
570 | text-align: center;
571 | margin-top: 40%;
572 | color: rgba(103, 212, 212, 0.39);
573 | font-weight: lighter;
574 | font-size: 20px;
575 | }
--------------------------------------------------------------------------------
/bin/css/wave-doing.css:
--------------------------------------------------------------------------------
1 |
2 | .wave {
3 | border: 1px solid #1ee3ff;
4 | z-index: -23;
5 | opacity: .4;
6 | position: absolute;
7 | top: 3%;
8 | left: 50%;
9 | background: linear-gradient(70deg, rgba(26, 215, 249, 0.22), rgba(94, 236, 228, 0.2));
10 | width: 2500px;
11 | height: 2500px;
12 | margin-left: -1250px;
13 | margin-top: 100px;
14 | -webkit-transform-origin: 50% 48%;
15 | transform-origin: 50% 48%;
16 | border-radius: 40%;
17 | animation: drift 8000ms infinite linear;
18 | }
19 |
20 |
21 | .wave.-five {
22 | animation: drift 3000ms infinite linear;
23 | }
24 |
25 | .wave.-four {
26 | animation: drift 4000ms infinite linear;
27 | }
28 |
29 | .wave.-three {
30 |
31 | animation: drift 5000ms infinite linear;
32 | }
33 |
34 | .wave.-two {
35 | animation: drift 7000ms infinite linear;
36 | opacity: .3;
37 | background: rgba(56, 255, 220, 0.19);
38 | }
39 |
40 |
41 | @keyframes drift {
42 | from {
43 | transform: rotate(0deg);
44 | }
45 | from {
46 | transform: rotate(360deg);
47 | }
48 | }
49 |
50 |
--------------------------------------------------------------------------------
/bin/img/ICON.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/img/ICON.ico
--------------------------------------------------------------------------------
/bin/img/ICON.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/img/ICON.png
--------------------------------------------------------------------------------
/bin/img/cpu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/img/error.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/img/file.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/img/loading.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/img/ok.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/img/remove.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/bin/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limitPNG
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
limitPNG BETA5
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | -
31 | x
32 |
33 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
拖入 PNG 图片进来吧
58 |
59 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
78 |
{{z.name}}
79 |
80 |
{{z.size_old | filter_file_size }}
81 |
{{z.size_new | filter_file_size }} ←
83 | {{z.size_new/z.size_old | filter_file_size_pre }}
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 | 有损快
105 | 高质量
106 | 低质量
107 | 256 高
108 | 256 低
109 | 极低色
110 |
111 |
112 |
113 |
114 | 快速
115 | 强力
116 | 极限
117 |
118 |
119 |
120 |
121 | {{(mode=='limit')?'极限':'压缩'}}
122 |
123 |
124 |
125 | 覆盖源
126 | 添加后缀
127 | 输出到...
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/bin/lib/sine-waves.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/bin/lib/sine-waves.js
--------------------------------------------------------------------------------
/bin/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name" : "LimitPNG",
3 | "version" : "0.1.0",
4 | "main" : "start.js"
5 | }
--------------------------------------------------------------------------------
/bin/start.js:
--------------------------------------------------------------------------------
1 | const electron = require('electron');
2 | const path = require('path');
3 | const ipcMain = electron.ipcMain
4 | const Tray = electron.Tray;
5 | // Module to control application life.
6 | const {app} = electron;
7 | // Module to create native browser window.
8 | const {BrowserWindow} = electron;
9 |
10 | // Keep a global reference of the window object, if you don't, the window will
11 | // be closed automatically when the JavaScript object is garbage collected.
12 | let win;
13 | console.log(path.resolve());
14 | function createWindow() {
15 | // Create the browser window.
16 |
17 |
18 | win = new BrowserWindow({
19 | width: 500,
20 | height: 600,
21 | frame: false, //窗口边框
22 | transparent: true, //透明背景
23 | resizable: true,//可调边框
24 | minWidth:420,
25 | minHeight :250,
26 | title:"limitPNG",
27 | icon: path.join(__dirname,'/img/ICON.ico')
28 |
29 | });
30 |
31 | // and load the index.html of the app.
32 | win.loadURL(`file://${__dirname}/index.html`);
33 |
34 | // Open the DevTools.
35 | // win.webContents.openDevTools();
36 |
37 | // Emitted when the window is closed.
38 | win.on('closed', () => {
39 | // Dereference the window object, usually you would store windows
40 | // in an array if your app supports multi windows, this is the time
41 | // when you should delete the corresponding element.
42 |
43 |
44 |
45 |
46 | win = null;
47 | });
48 | }
49 |
50 | // This method will be called when Electron has finished
51 | // initialization and is ready to create browser windows.
52 | // Some APIs can only be used after this event occurs.
53 | app.on('ready', createWindow);
54 |
55 | // Quit when all windows are closed.
56 | app.on('window-all-closed', () => {
57 | // On OS X it is common for applications and their menu bar
58 | // to stay active until the user quits explicitly with Cmd + Q
59 | if (process.platform !== 'darwin') {
60 | app.quit();
61 | }
62 | });
63 |
64 | app.on('activate', () => {
65 | // On OS X it's common to re-create a window in the app when the
66 | // dock icon is clicked and there are no other windows open.
67 | if (win === null) {
68 | createWindow();
69 | }
70 | });
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 | // In this file you can include the rest of your app's specific main process
80 | // code. You can also put them in separate files and require them here.
81 |
82 |
83 |
84 |
85 | ipcMain.on('exit', function () {
86 | app.quit();
87 | });
88 |
89 |
90 | ipcMain.on('minimize', function () {
91 | win.minimize()
92 | });
--------------------------------------------------------------------------------
/components/main-list.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | this is template body
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by bgllj on 2016/5/31.
3 | */
4 |
5 | import Vue from "vue";
6 | import VueDragula from 'vue-dragula';
7 |
8 | Vue.use(VueDragula);
9 | import "vue-dragula/styles/dragula.min.css"
10 | import path from "path"
11 | import fs from "fs"
12 | import fsex from "fs-extra";
13 | import "./limitPNG.js"
14 | import "./report"
15 | import electron from 'electron'
16 | var remote = electron.remote;
17 | var dialog = remote.dialog;
18 | var shell = electron.shell;
19 | var ipcRenderer = electron.ipcRenderer;
20 | var shell = electron.shell;
21 |
22 |
23 | window.path = path;
24 | window.fs = fs;
25 | window.remote = remote;
26 | window.dialog = dialog;
27 | window.shell = shell
28 | window.Vue = Vue;
29 |
30 |
31 | window.filter_file_size = function (value)
32 | {
33 | if (value < 1024)
34 | {
35 | return value + " B";
36 | }
37 |
38 | let z = (value / 1024);
39 | if (z > 1000)
40 | {
41 | return (z / 1024).toFixed(2) + " MB";
42 | }
43 | else
44 | {
45 | return (z).toFixed(2) + " KB";
46 | }
47 | }
48 |
49 | Vue.filter('filter_file_size', filter_file_size)
50 |
51 |
52 | window.filter_file_size_pre = function (value)
53 | {
54 | let pre;
55 | if (value > 1.001)
56 | {
57 | return "+ " + Math.floor((value - 1) * 100) + "%"
58 | } else
59 | {
60 | pre = (1 - value)
61 | if (pre < 0.01 && pre > 0)
62 | {
63 | return "- " + (pre * 100).toFixed(2) + "%";
64 | } else
65 | {
66 | return "- " + Math.floor(pre * 100) + "%";
67 | }
68 |
69 | }
70 | }
71 |
72 | Vue.filter('filter_file_size_pre', filter_file_size_pre)
73 |
74 |
75 | var file_data = [];
76 |
77 | //todo test
78 | // file_data
79 | // = [{
80 | // name: "tas都是ss1.png",
81 | // size_old: "21233",
82 | // size_new: null,
83 | // doing: true
84 | // },
85 | // {
86 | // name: "QWEERsadf.png",
87 | // size_old: "30000",
88 | // size_new: null
89 | // },
90 | // {
91 | // name: "vvvvv.png",
92 | // size_old: "3233",
93 | // size_new: "5000",
94 | // done: true
95 | //
96 | // },
97 | // {
98 | // name: "sequence-123.png",
99 | // size_old: "2133233",
100 | // size_new: "193133",
101 | // done: true
102 | // },
103 | // {
104 | // name: "DDS. L'Huilier.png",
105 | // size_old: "2133233",
106 | // size_new: null,
107 | // error: true
108 | // }
109 | // ]
110 |
111 |
112 | var main_list = new Vue(
113 | {
114 | el: "#vue_app",
115 | data: {
116 | test: "ddddd",
117 | list: file_data,
118 | loading_file: false,
119 | tasks_doing: false,
120 | time: 0,
121 |
122 |
123 | //----
124 | mode: "limit",
125 | out: "-suffix",
126 | thread: 2,
127 |
128 | },
129 | methods: {
130 |
131 | openImg_right: function (e, msg)
132 | {
133 | if (e.button == 2)
134 | shell.openItem(msg)
135 | },
136 |
137 | openImg: function (msg)
138 | {
139 | shell.openItem(msg)
140 | },
141 | open_file: function ()
142 | {
143 | if (v.tasks_doing)
144 | {
145 | alert("任务进行中,不能打开文件")
146 | return;
147 | }
148 |
149 | dialog.showOpenDialog(
150 | {title: "打开文件", properties: ["openFile", "multiSelections"]},
151 | (files)=>
152 | {
153 | if (files != undefined && files.length > 0)
154 | {
155 | var newList = [];
156 | for (let i = 0; i < files.length; i++)
157 | {
158 | var stat = fs.lstatSync(files[i]);
159 | if (stat.isDirectory())
160 | {
161 | _scanFiles(files[i], fs.readdirSync(files[i]), newList);
162 | }
163 | else
164 | {
165 | if (ifPngFile(files[i]))
166 | {
167 | newList.push({path: files[i], size: getFileSize(files[i])});
168 | }
169 | }
170 | }
171 | _put_list(newList);
172 | }
173 | }
174 | )
175 | },
176 | remove_all: function ()
177 | {
178 | if (v.tasks_doing == false)
179 | {
180 | this.list = [];
181 | file_data = [];
182 | }
183 | }
184 |
185 |
186 | },
187 | computed: {
188 | // 一个计算属性的 getter
189 | list_size_old: function ()
190 | {
191 | let size = 0;
192 | for (let i = 0; i < this.list.length; i++)
193 | {
194 | size = size + +this.list[i].size_old;
195 | }
196 | return size;
197 | },
198 | list_size_new: function ()
199 | {
200 | let size = 0;
201 | for (let i = 0; i < this.list.length; i++)
202 | {
203 | size = size + +this.list[i].size_new;
204 | }
205 | return size;
206 | },
207 | out_dir: function ()
208 | {
209 | if (this.out[0] != undefined && this.out[0] != "-")
210 | {
211 | return this.out;
212 | } else
213 | {
214 | return "";
215 | }
216 |
217 | }
218 | }
219 | ,
220 | created: function ()
221 | {
222 | Vue.vueDragula.options('my-bag', {
223 | direction: 'vertical'
224 | })
225 | }
226 |
227 | }
228 | )
229 |
230 |
231 | window.v = main_list
232 | window.a = file_data
233 |
234 | const holder = document.getElementById('main_body');
235 |
236 | holder.ondragover = () =>
237 | {
238 | return false;
239 | };
240 | holder.ondragleave = holder.ondragend = () =>
241 | {
242 | return false;
243 | };
244 | holder.ondrop = (e) =>
245 | {
246 | e.preventDefault();
247 | const file = e.dataTransfer.files[0];
248 | console.log(e.dataTransfer.files);
249 | // console.log('File you dragged here is', file.path);
250 | main_list.loading_file = true;
251 |
252 | var ff = e.dataTransfer.files
253 | setTimeout(function ()
254 | {
255 | putFile2list(ff);
256 | main_list.loading_file = false;
257 | }, 100)
258 |
259 |
260 | return false;
261 | };
262 |
263 |
264 | function putFile2list(files)
265 | {
266 |
267 | if (files == undefined || files.length < 1)
268 | {
269 | return
270 | }
271 |
272 | var newList = [];
273 |
274 | for (let i = 0; i < files.length; i++)
275 | {
276 | var stat = fs.lstatSync(files[i].path);
277 | if (stat.isDirectory())
278 | {
279 | _scanFiles(files[i].path, fs.readdirSync(files[i].path), newList);
280 | }
281 | else
282 | {
283 | if (ifPngFile(files[i].path))
284 | {
285 | newList.push({path: files[i].path, size: files[i].size});
286 | }
287 |
288 | }
289 | }
290 |
291 | _put_list(newList);
292 | console.log("--------")
293 | console.log(newList)
294 | // file_data
295 | }
296 |
297 | function _put_list(newList)
298 | {
299 | if (v.tasks_doing)
300 | {
301 | alert("任务进行中,不能添加文件")
302 | return;
303 | } else
304 | {
305 |
306 | if (file_data.length > 0)
307 | {
308 | if (file_data[0].done == true)
309 | {
310 | file_data = [];
311 | }
312 | }
313 |
314 |
315 | }
316 |
317 |
318 | for (let i = 0; i < newList.length; i++)
319 | {
320 |
321 | if (_existPath(newList[i].path,file_data) == false)
322 | {
323 | file_data.push({
324 | name: path.basename(newList[i].path),
325 | size_old: newList[i].size,
326 | size_new: 0,
327 | path: newList[i].path,
328 | new_File: "",
329 | doing: false,
330 | done: false,
331 | error: false,
332 | error_info: "",
333 | time_consum: null,
334 | log: "",
335 |
336 | });
337 | }
338 |
339 |
340 | }
341 |
342 |
343 | if (file_data !== undefined && file_data.length > 0)
344 | {
345 | main_list.list = file_data;
346 |
347 | } else
348 | {
349 | alert("拖入的文件没有图片")
350 | }
351 |
352 |
353 | function _existPath(_path, objArray)
354 | {
355 | for (let i = 0; i < objArray.length; i++)
356 | {
357 | if (objArray[i].path == _path)
358 | {
359 | return true;
360 | }
361 | }
362 |
363 | return false;
364 | }
365 |
366 | }
367 | function _scanFiles(dirPath, _files, newList)
368 | {
369 | for (let i = 0; i < _files.length; i++)
370 | {
371 | let _path = path.join(dirPath, _files[i])
372 |
373 | let stat = fs.lstatSync(_path);
374 | if (stat.isDirectory())
375 | {
376 | _scanFiles(_path, fs.readdirSync(_path), newList);
377 | }
378 | else
379 | {
380 | if (ifPngFile(_path))
381 | {
382 | newList.push({path: _path, size: stat.size});
383 | }
384 | }
385 | }
386 | }
387 |
388 | function ifPngFile(fileName)
389 | {
390 | if (path.extname(fileName).toLocaleLowerCase() == ".png")
391 | {
392 | return true
393 | } else
394 | {
395 | return false
396 | }
397 | }
398 |
399 |
400 | window.doLimiteByfile_data = function ()
401 | {
402 | if (v.tasks_doing > 0)
403 | {
404 | return
405 | } else
406 | {
407 |
408 | }
409 |
410 | v.time = Date.now();
411 | var tasks = []
412 | window.tasks = tasks;
413 | for (let i = 0; i < main_list.list.length; i++)
414 | {
415 | let exp = new LimitPNG(main_list.list[i].path);
416 | console.log(exp)
417 |
418 | if (exp.pngFile != "")
419 | {
420 | tasks[i] = function (resolve, reject)
421 | {
422 | main_list.list[i].doing = true;
423 | let _do_func = function (newFile, old_size, new_size, time_consum, err, log)
424 | {
425 | main_list.list[i].doing = false;
426 | main_list.list[i].done = true;
427 | main_list.list[i].size_new = new_size;
428 | main_list.list[i].time_consum = time_consum;
429 | main_list.list[i].new_File = newFile;
430 | main_list.list[i].log = log;
431 | if (err != undefined && err != "")
432 | {
433 | main_list.list[i].error = true;
434 | main_list.list[i].error_info = err;
435 | }
436 | v.tasks_doing = v.tasks_doing - 1;
437 | if (v.tasks_doing < 1)
438 | {
439 | v.time = (Date.now() - v.time)
440 | }
441 | if (resolve != undefined)
442 | {
443 | resolve(0);
444 | }
445 | }
446 | exp.doDefault(main_list.out, main_list.mode, _do_func);
447 | };
448 | }
449 | }
450 |
451 |
452 | //单线程
453 | // _do_tasks(tasks)
454 |
455 |
456 | //多线程
457 | // function _do_tasks_N(n)
458 | // {
459 | // for (let i = 0; i < tasks.length / n; i++)
460 | // {
461 | // _do_tasks(tasks.slice(n*i , n*i +n))
462 | // }
463 | // }
464 |
465 |
466 | var stack = 2;
467 | if (v.thread != undefined && v.thread > 0 && v.thread < 16)
468 | {
469 | stack = v.thread;
470 | }
471 |
472 | v.tasks_doing = tasks.length;
473 | cheakTasks()
474 | function cheakTasks()
475 | {
476 |
477 | while (stack > 0 && tasks.length > 0)
478 | {
479 | tasks.shift()(function ()
480 | {
481 | stack++;
482 | cheakTasks();
483 | });
484 | stack--;
485 | }
486 |
487 | }
488 |
489 |
490 | //双线程
491 | // var _task_piceA=[];
492 | // var _task_piceB=[];
493 | // for (let i = 0; i < tasks.length; i++)
494 | // {
495 | // if (i%2 ==0)
496 | // {
497 | // _task_piceB.push(tasks[i]);
498 | // }else
499 | // {
500 | // _task_piceA.push(tasks[i]);
501 | // }
502 | // }
503 | //
504 | // _do_tasks(_task_piceA);
505 | // _do_tasks(_task_piceB);
506 |
507 |
508 | function _do_tasks(_tasks)
509 | {
510 | let promises = [];
511 | for (let i = 0; i < _tasks.length; i++)
512 | {
513 | if (i == 0)
514 | {
515 | promises[0] = new Promise(_tasks[0])
516 | }
517 | else
518 | {
519 | promises[i] = new Promise(function (resolve, reject)
520 | {
521 | promises[i - 1].then(function () {_tasks[i](resolve, reject);})
522 | })
523 | }
524 | }
525 | }
526 |
527 | }
528 |
529 |
530 | window.minimize = ()=>
531 | {
532 | ipcRenderer.send('minimize');
533 | }
534 |
535 | window.exit = ()=>
536 | {
537 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp"
538 | temp = path.join(temp, "limitPNG_temp");
539 | fsex.removeSync(temp)
540 |
541 | ipcRenderer.send('exit');
542 | }
543 |
544 |
545 | var _select_out_op = false;
546 |
547 |
548 | window.select_out_blur = ()=>
549 | {
550 | if (_select_out_op = true)
551 | {
552 | _select_out_op = false;
553 | }
554 | }
555 |
556 | window.select_out = ()=>
557 | {
558 | if (_select_out_op)
559 | {
560 | var s = document.getElementById("outFile");
561 | if (s.selectedIndex == 2)
562 | {
563 | dialog.showOpenDialog(
564 | {title: "选择输出文件夹", defaultPath: v.our_dir, properties: ["openDirectory"]},
565 | (e)=>
566 | {
567 | if (e != undefined && e.length > 0)
568 | {
569 |
570 | s[2].text = "输出到:" + e[0];
571 | s[2].value = e[0]
572 | v.out = e[0];
573 | }
574 | }
575 | )
576 | }
577 | }
578 |
579 | _select_out_op = !_select_out_op;
580 |
581 | }
582 |
583 |
584 | window.openUrl = function (url)
585 | {
586 | console.log(url)
587 | shell.openExternal(url);
588 | }
589 |
590 |
591 |
--------------------------------------------------------------------------------
/limitPNG.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by bgllj on 2016/6/3.
3 | */
4 |
5 | import child_process from "child_process";
6 | import path from "path";
7 | import os from "os";
8 | import fs from "fs"
9 | import fsex from "fs-extra";
10 |
11 | var exec = child_process.exec;
12 |
13 | window.child_process = child_process;
14 | window.exec = exec;
15 | window.os = os;
16 |
17 |
18 | function log(_in)
19 | {
20 | console.log(_in)
21 | }
22 |
23 | log.err = function (_in)
24 | {
25 | console.error(_in);
26 | }
27 | log.info = function (_in)
28 | {
29 | console.info(_in);
30 | }
31 |
32 | log.test = function (_in)
33 | {
34 | console.info(_in);
35 | }
36 |
37 |
38 | window.LimitPNG = LimitPNG;
39 |
40 | function LimitPNG(in_pngFile)
41 | {
42 | this.pngFile = "";
43 |
44 | if (in_pngFile != undefined)
45 | {
46 | if (fileExists(in_pngFile))
47 | {
48 | this.pngFile = path.resolve(in_pngFile);
49 | this.tempFile = this.allocTemp();
50 | // this.color = this.getPngColorInfo();
51 | }
52 | else
53 | {
54 | console.error("file not exist:" + in_pngFile)
55 | }
56 |
57 |
58 | }
59 |
60 | return this;
61 | }
62 |
63 |
64 | LimitPNG.prototype.doDefault = function (option, mode, callBack)
65 | {
66 | //this.do([PROCE.truePNG(1), PROCE.pngout(2), PROCE.cryopng(1), PROCE.pngwolf(1),PROCE.zopflipng(1)]) // 移除PROCE.cryopng(1)
67 | if (mode == "limit")
68 | {
69 | this.do([PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
70 | }
71 | else if (mode == "high")
72 | {
73 | this.do([PROCE.pngwolf(1), PROCE.ect(0)], option, callBack)
74 | }
75 | else if (mode == "quick")
76 | {
77 | this.do([PROCE.ect(0)], option, callBack)
78 | }
79 | else if (mode == "256-high")
80 | {
81 | this.do([PROCE.pngquant(22, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
82 | }
83 | else if (mode == "256-low")
84 | {
85 | this.do([PROCE.pngquant(24, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
86 | }
87 | else if (mode == "256-lowest")
88 | {
89 | this.do([PROCE.pngquant(25, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
90 | }
91 | else if (mode == "lossy-high")
92 | {
93 | this.do([PROCE.pngquant(26, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
94 | }
95 | else if (mode == "lossy-low")
96 | {
97 | this.do([PROCE.pngquant(27, true), PROCE.truePNG(1), PROCE.pngout(0, true), PROCE.pngwolf(1), PROCE.zopflipng(1)], option, callBack)
98 | }
99 | else if (mode == "lossy-quick")
100 | {
101 | this.do([PROCE.pngquant(26, true), PROCE.ect(0)], option, callBack)
102 | }
103 |
104 |
105 | }
106 |
107 | LimitPNG.prototype.doDefaultLossy = function ()
108 | {
109 | this.do([PROCE.pngquant(3, true), PROCE.truePNG(1), PROCE.pngout(2), PROCE.pngwolf(1), PROCE.zopflipng(1)])
110 | }
111 |
112 |
113 | LimitPNG.prototype.do = function (in_processors, outOption, callBack)
114 | {
115 | var sizeLog = "";
116 | if (in_processors == undefined)
117 | {
118 | return null;
119 | }
120 | try
121 | {
122 | //fs.createReadStream(this.pngFile).pipe(fs.createWriteStream(this.tempFile));
123 | fsex.copySync(this.pngFile, this.tempFile);
124 | } catch (e)
125 | {
126 | console.error(e)
127 | return null;
128 | }
129 |
130 |
131 | //fsex.copySync(this.pngFile, this.tempFile);
132 | //fs.rename(this.tempFile, this.tempFile+"2222222");
133 |
134 |
135 | function _doChild(_proce, _tempFile, _i, resolve, reject)
136 | {
137 | try
138 | {
139 | log(_i + " ===================================")
140 | let do_cmd = '"' + path.resolve(".\\resources\\tools\\" + _proce.exeName) + '" ' + _proce.cmd(_tempFile);
141 | log("do_cmd " + _i + " : " + do_cmd);
142 | let child = child_process.exec(do_cmd, {timeout: 3654321}, function (error, stdout, stderr)
143 | {
144 | log.test("sout:" + _i + "\n" + stdout);
145 | if (error != undefined)
146 | {
147 | console.error("serror:" + _i + "\n" + error);
148 | }
149 | sizeLog = sizeLog + "->[" + _proce.exeName + " : " + getFileSize(_tempFile) + "]"
150 | log("sizeLog:" + _i + "\n" + sizeLog);
151 | resolve(_i);
152 | });
153 |
154 |
155 | child.stdout.on('data', (data) =>
156 | {
157 | log.test(`stdout: ${data}`);
158 | });
159 |
160 |
161 | }
162 | catch (e)
163 | {
164 |
165 | log("catch-e:")
166 | log.err(e)
167 | sizeLog = sizeLog + "->[" + _proce.exeName + " : " + getFileSize(_tempFile) + "]"
168 | log("sizeLog:" + _i + "\n" + sizeLog);
169 | if (fileExists(_tempFile) != true)
170 | {
171 | reject(_i);
172 | }
173 | }
174 | }
175 |
176 |
177 | var _tempFile = this.tempFile;
178 | var _pngFile = this.pngFile;
179 | var old_size = getFileSize(_tempFile);
180 | var time_start = Date.now();
181 | var promises = [];
182 | var funcs = [];
183 | for (let i = 0; i < in_processors.length; i++)
184 | {
185 | log(i + "--发出")
186 | let _tempFile = this.tempFile;
187 |
188 | if (i == 0)
189 | {
190 | promises[i] = new Promise(function (resolve, reject)
191 | {
192 | _doChild(in_processors[i], _tempFile, i, resolve, reject);
193 | });
194 |
195 | } else
196 | {
197 | promises[i] = new Promise(function (resolve, reject)
198 | {
199 | promises[i - 1].then(
200 | function (value)
201 | {
202 | _doChild(in_processors[i], _tempFile, i, resolve, reject);
203 | }
204 | )
205 | })
206 |
207 | }
208 |
209 | }
210 |
211 |
212 | promises[promises.length - 1].then(
213 | function (value)
214 | {
215 | var new_size = getFileSize(_tempFile);
216 | var time_consum = Date.now() - time_start;
217 | var err = "";
218 |
219 |
220 | if (new_size > old_size)
221 | {
222 | new_size = old_size;
223 | _tempFile =_pngFile;
224 | }
225 |
226 |
227 | if (outOption == "-overwrite")
228 | {
229 | try
230 | {
231 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(_pngFile));
232 | } catch (e)
233 | {
234 | console.error(e)
235 | err = "无法写入到原文件。" + e
236 | callBack(_pngFile, old_size, new_size, time_consum, err);
237 | return null;
238 | }
239 | }
240 | else if (outOption == "-suffix")
241 | {
242 | var new_path = path.join(path.dirname(_pngFile), path.parse(_pngFile).name + `_lim[${v.mode}].png`)
243 | try
244 | {
245 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(new_path));
246 | } catch (e)
247 | {
248 | console.error(e)
249 | err = "无法写入到文件 -suffix。" + e
250 | callBack(new_path, old_size, new_size, time_consum, err);
251 | return null;
252 | }
253 | }
254 | else if (fileExists(outOption))
255 | {
256 | var new_path = path.join(outOption, path.parse(_pngFile).base)
257 |
258 | try
259 | {
260 | fs.createReadStream(_tempFile).pipe(fs.createWriteStream(new_path));
261 | } catch (e)
262 | {
263 | console.error(e)
264 | err = "无法写入到文件 -out。" + e
265 | callBack(new_path, old_size, new_size, time_consum, err);
266 | return null;
267 | }
268 | }
269 |
270 |
271 | log("sizeLog:\n" + sizeLog);
272 | log("DONE===================================")
273 |
274 |
275 | callBack(new_path, old_size, new_size, time_consum, err, sizeLog);
276 | return 0;
277 | }
278 | )
279 |
280 | }
281 |
282 |
283 | LimitPNG.prototype.allocTemp = function ()
284 | {
285 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp"
286 | temp = path.join(temp, "limitPNG_temp");
287 |
288 |
289 | if (fileExists(temp) == false)
290 | {
291 | fs.mkdirSync(temp)
292 | }
293 | if (fileExists(temp) == false)
294 | {
295 | console.error("无法创建临时文件夹 :" + temp);
296 | return null
297 | }
298 |
299 |
300 | temp = path.join(temp, Date.now() + "_" + path.basename(this.pngFile));
301 | return temp;
302 | }
303 |
304 |
305 | LimitPNG.prototype.getPngColorInfo = function ()//By Pngout
306 | {
307 | //let outInfo = child_process.execSync(path.resolve(".\\resources\\tools\\pngout.exe")+" -l" + ' "' + this.pngFile + '"')
308 | try
309 | {
310 | var outInfo = child_process.execSync('"' + path.resolve(".\\resources\\tools\\pngout.exe") + '"' + " -l " + '"' + this.pngFile + '"')
311 | } catch (e)
312 | {
313 | console.error(e)
314 | return null
315 | }
316 |
317 | var c = /c./.exec(String(outInfo));
318 | if (c != null)
319 | {
320 | switch (c[0])
321 | {
322 | case "c0":
323 | return {code: 0, color: "Gray"};
324 | case "c1":
325 | return {code: 2, color: "RGB"};
326 | case "c3":
327 | return {code: 3, color: "pal"};
328 | case "c4":
329 | return {code: 4, color: "Gray+Alpha"};
330 | case "c6":
331 | return {code: 6, color: "RGB+Alpha"};
332 | }
333 | }
334 | return null;
335 | }
336 |
337 |
338 | // 定义 PNG 处理器:
339 |
340 |
341 | var PROCE = {};
342 | window.PROCE = PROCE;
343 |
344 |
345 | //--- truePNG -------------------------------------
346 | PROCE.truePNG = function (opIndex, isLossy)
347 | {
348 | var re = {};
349 | if (isLossy)
350 | {
351 | re.op = PROCE.truePNG.lossyOps[opIndex].op;
352 | }
353 | else
354 | {
355 | re.op = PROCE.truePNG.lossLessOps[opIndex].op;
356 | }
357 |
358 |
359 | re.cmd = function (in_path)
360 | {
361 | // console.log(this)
362 | return ' "' + in_path + '" ' + re.op;
363 | };
364 |
365 | re.exeName = "TruePNG.exe";
366 | return re;
367 | }
368 |
369 | PROCE.truePNG.lossLessOps = [
370 | {op: "-f0,5 -i0 -g0 -md remove all -zc8 -zm8 -zw7 -zs0,1 -quiet -force -y", name: "指定最佳"},
371 | {op: "-o max -quiet", name: "自动最佳"}
372 | ];
373 |
374 | PROCE.truePNG.lossyOps = [
375 | {op: "-f0,5 -i0 -g0 -md remove all -zc8 -zm8 -zw7 -zs0,1 -quiet -force -y", name: ""},
376 | ];
377 |
378 |
379 | //--- pngout -------------------------------------
380 | PROCE.pngout = function (opIndex, isLossy)
381 | {
382 | var re = {};
383 | if (isLossy)
384 | {
385 | re.op = PROCE.pngout.lossyOps[opIndex].op;
386 | }
387 | else
388 | {
389 | re.op = PROCE.pngout.lossLessOps[opIndex].op;
390 | }
391 |
392 |
393 | re.cmd = function (in_path)
394 | {
395 | // console.log(this)
396 | return ' "' + in_path + '" ' + re.op;
397 | };
398 |
399 | re.exeName = "pngout.exe";
400 | return re;
401 | }
402 |
403 | PROCE.pngout.lossLessOps = [
404 | {op: "-f5 -s0 -k0 -q -y -force", name: "RGBA 最佳"},
405 | {op: "-f0 -s0 -k0 -q -y -force", name: "pal 最佳"},
406 | {op: "", name: "自动最佳"}
407 | ];
408 |
409 | PROCE.pngout.lossyOps = [{op: "-c3 -quiet", name: "减色"}];
410 |
411 |
412 | //--- cryopng -------------------------------------
413 | PROCE.cryopng = function (opIndex, isLossy)
414 | {
415 | var re = {};
416 | if (isLossy)
417 | {
418 | re.op = PROCE.cryopng.lossyOps[opIndex].op;
419 | }
420 | else
421 | {
422 | re.op = PROCE.cryopng.lossLessOps[opIndex].op;
423 | }
424 |
425 | re.cmd = function (in_path)
426 | {
427 | // console.log(this)
428 | return ' "' + in_path + '" ' + re.op;
429 | };
430 |
431 | re.exeName = "cryopng.exe";
432 | return re;
433 | }
434 |
435 | PROCE.cryopng.lossLessOps = [
436 | {op: "-f1 -force -quiet -i0 -zc1 -zm1 -zs3 -zw32k -nx ", name: "RGBA 最佳"},
437 | {op: "-o7 -force ", name: "自动最佳"},
438 | {op: "", name: "自动"}
439 | ];
440 |
441 | PROCE.cryopng.lossyOps = [];
442 |
443 | //--- pngwolf -------------------------------------
444 | PROCE.pngwolf = function (opIndex, isLossy)
445 | {
446 | var re = {};
447 | if (isLossy)
448 | {
449 | re.op = PROCE.pngwolf.lossyOps[opIndex].op;
450 | }
451 | else
452 | {
453 | re.op = PROCE.pngwolf.lossLessOps[opIndex].op;
454 | }
455 |
456 | re.cmd = function (in_path)
457 | {
458 | // console.log(this)
459 | return '--in="' + in_path + '" ' + '--out="' + in_path + '" ' + re.op;
460 |
461 |
462 | };
463 |
464 | re.exeName = "pngwolf.exe";
465 | return re;
466 | }
467 |
468 | PROCE.pngwolf.lossLessOps = [
469 | {
470 | op: "--zlib-level=8 --zlib-strategy=1 --exclude-original --exclude-singles --exclude-heuristic --max-evaluations=1",
471 | name: "RGB 最佳"
472 | },
473 | {op: "", name: "自动最佳"},
474 | ];
475 |
476 | PROCE.pngwolf.lossyOps = [];
477 |
478 | //--- zopflipng -------------------------------------
479 | PROCE.zopflipng = function (opIndex, isLossy)
480 | {
481 | var re = {};
482 | if (isLossy)
483 | {
484 | re.op = PROCE.zopflipng.lossyOps[opIndex].op;
485 | }
486 | else
487 | {
488 | re.op = PROCE.zopflipng.lossLessOps[opIndex].op;
489 | }
490 |
491 | re.cmd = function (in_path)
492 | {
493 | // console.log(this)
494 | return re.op + ' "' + in_path + '" ' + ' "' + in_path + '" ';
495 | };
496 |
497 | re.exeName = "zopflipng.exe";
498 | return re;
499 | }
500 |
501 | PROCE.zopflipng.lossLessOps = [
502 | {op: "-y --iterations=35 --filters=01234mepb", name: "多次重最佳"},
503 | {op: "-m -y", name: "自动最佳"},
504 | ];
505 |
506 | PROCE.zopflipng.lossyOps = [];
507 |
508 |
509 | //--- pngquant -------------------------------------
510 | PROCE.pngquant = function (opIndex, isLossy)
511 | {
512 | var re = {};
513 | if (isLossy)
514 | {
515 | re.op = PROCE.pngquant.lossyOps[opIndex].op;
516 | }
517 | else
518 | {
519 | re.op = PROCE.pngquant.lossLessOps[opIndex].op;
520 | }
521 |
522 | re.cmd = function (in_path)
523 | {
524 | // console.log(this)
525 | return '-o "' + in_path + ' " ' + re.op + ' "' + in_path + '"';
526 | };
527 |
528 | re.exeName = "pngquant.exe";
529 | return re;
530 | }
531 |
532 | PROCE.pngquant.lossLessOps = [];
533 |
534 | PROCE.pngquant.lossyOps = [
535 | {op: " --force --speed 1 --quality 0-1 ", name: "Q0"}, // 0
536 | {op: " --force --speed 1 --quality 0-10 ", name: "Q10"}, // 1
537 | {op: " --force --speed 1 --quality 20-30 ", name: "Q30"}, // 2
538 | {op: " --force --speed 1 --quality 10-20 ", name: "Q20"}, // 3
539 | {op: " --force --speed 1 --quality 30-40 ", name: "Q40"}, // 4
540 | {op: " --force --speed 1 --quality 40-50 ", name: "Q50"}, // 5
541 | {op: " --force --speed 1 --quality 50-60 ", name: "Q60"}, // 6
542 | {op: " --force --speed 1 --quality 60-70 ", name: "Q70"}, // 7
543 | {op: " --force --speed 1 --quality 70-80 ", name: "Q80"}, // 8
544 | {op: " --force --speed 1 --quality 80-90 ", name: "Q90"}, // 9
545 | {op: " --force --speed 1 --quality 90-100 ", name: "Q100"}, // 10
546 | {op: " --force --speed 1 --quality 0-1 256", name: "256-Q0"}, // 11
547 | {op: " --force --speed 1 --quality 0-10 256", name: "256-Q10"}, // 12
548 | {op: " --force --speed 1 --quality 20-30 256", name: "256-Q30"}, // 13
549 | {op: " --force --speed 1 --quality 10-20 256", name: "256-Q20"}, // 14
550 | {op: " --force --speed 1 --quality 30-40 256", name: "256-Q40"}, // 15
551 | {op: " --force --speed 1 --quality 40-50 256", name: "256-Q50"}, // 16
552 | {op: " --force --speed 1 --quality 50-60 256", name: "256-Q60"}, // 17
553 | {op: " --force --speed 1 --quality 60-70 256", name: "256-Q70"}, // 18
554 | {op: " --force --speed 1 --quality 70-80 256", name: "256-Q80"}, // 19
555 | {op: " --force --speed 1 --quality 80-90 256", name: "256-Q90"}, // 20
556 | {op: " --force --speed 1 --quality 90-100 256", name: "256-Q100"}, // 21
557 | {op: " --force --speed 1 256", name: "256-high"}, // 22
558 | {op: " --force --speed 1 --quality 40-80 256", name: "256-middle"}, // 23
559 | {op: " --force --speed 1 --quality 0-40 256", name: "256-low"}, // 24
560 | {op: " --force --speed 1 --quality 0-1 --posterize 4 256", name: "256-lowest"}, // 25
561 | {op: " --force --speed 1 ", name: "lossy-high"}, // 26
562 | {op: " --force --speed 1 --quality 0-40", name: "lossy-low"}, // 27
563 |
564 |
565 | ];
566 |
567 |
568 | //--- ect -------------------------------------
569 | PROCE.ect = function (opIndex, isLossy)
570 | {
571 | var re = {};
572 | if (isLossy)
573 | {
574 | re.op = PROCE.ect.lossyOps[opIndex].op;
575 | }
576 | else
577 | {
578 | re.op = PROCE.ect.lossLessOps[opIndex].op;
579 | }
580 |
581 | re.cmd = function (in_path)
582 | {
583 | // console.log(this)
584 | return re.op + ' "' + in_path + '" '
585 | };
586 |
587 | re.exeName = "ect.exe";
588 | return re;
589 | }
590 |
591 | PROCE.ect.lossLessOps = [
592 | {op: "-s3", name: "最好"},
593 | {op: "-s2", name: "平衡"},
594 | {op: "-s1", name: "快速"},
595 | ];
596 |
597 | PROCE.ect.lossyOps = [];
598 |
599 |
600 | // window.l = new LimitPNG("../test/1.png")//debug
601 |
602 | function fileExists(in_path)
603 | {
604 | try
605 | {
606 | fs.accessSync(in_path, fs.R_OK | fs.W_OK)
607 | } catch (e)
608 | {
609 | console.error(e);
610 | return false
611 | }
612 | return true;
613 | }
614 |
615 |
616 | window.getFileSize = function (in_path)
617 | {
618 | try
619 | {
620 | var stat = fs.statSync(in_path)
621 | } catch (e)
622 | {
623 | console.error(e);
624 | return null
625 | }
626 |
627 | return stat.size;
628 |
629 | }
630 |
631 |
632 | ///------------
633 |
634 |
635 | // cmds: {
636 | // //todo:还未完成所有参数:
637 | // delta_filters: {
638 | // name: "delta filters",
639 | // cmd: [
640 | // {op: "f0", name: "None", miltiSelect: "f"},
641 | // {op: "f1", name: "Sub", miltiSelect: "f"},
642 | // {op: "f2", name: "Up", miltiSelect: "f"},
643 | // {op: "f3", name: "Average", miltiSelect: "f"},
644 | // {op: "f4", name: "Paeth", miltiSelect: "f"},
645 | // {op: "f5", name: "Mixed", miltiSelect: "f"},
646 | // {op: "fe", name: "combination for smallest size"},
647 | // ]
648 | // },
649 | // }
650 |
651 |
652 |
--------------------------------------------------------------------------------
/move.bat:
--------------------------------------------------------------------------------
1 | copy /y app.asar release\limitPNG_64_beta5\resources\
2 | copy /y app.asar release\limitPNG_32_beta5\resources\
3 | pause
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "limit_png",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "electron bin"
8 | },
9 | "author": "",
10 | "license": "ISC",
11 | "devDependencies": {
12 | "babel-plugin-transform-runtime": "^6.9.0",
13 | "babel-preset-es2015": "^6.9.0",
14 | "babel-preset-stage-3": "^6.5.0",
15 | "file-loader": "^0.8.5",
16 | "url-loader": "^0.5.7",
17 | "vue-hot-reload-api": "^1.3.2",
18 | "vue-html-loader": "^1.2.2",
19 | "vue-loader": "^8.5.2",
20 | "vue-style-loader": "^1.0.0",
21 | "webpack": "^1.13.1"
22 | },
23 | "dependencies": {
24 | "dragula": "^3.7.1",
25 | "fs-extra": "^0.30.0",
26 | "vue": "^1.0.24",
27 | "vue-animated-list": "^1.0.2",
28 | "vuex": "^0.6.3"
29 | }
30 | }
--------------------------------------------------------------------------------
/report.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by bgllj on 2016/6/8.
3 | */
4 |
5 |
6 |
7 | window.getLog = function ()
8 | {
9 | let log = `
10 | 文件名
11 | 路径
12 | 原大小
13 | 新大小
14 | 模式
15 | 减少大小
16 | 减少比例
17 | 耗时
18 | 处理日志
19 | 错误
20 | `;
21 |
22 | let zs0 = v.list_size_old;
23 | let zs1 = v.list_size_new;
24 | let ts = 0;
25 | for (let i = 0; i < v.list.length; i++)
26 | {
27 | let z0 = v.list[i].size_old;
28 | let z1 = v.list[i].size_new;
29 | let t = (v.list[i].time_consum / 1000).toFixed(1);
30 | ts = +ts + +t;
31 |
32 | log = log + `
33 | ${v.list[i].name}
34 | ${v.list[i].path}
35 | ${filter_file_size(z0)} (${z0} B)
36 | ${filter_file_size(z1)} (${z1} B)
37 | [${v.mode}]
38 | ${filter_file_size(z1 - z0)}(${z1 - z0} B)
39 | ${filter_file_size_pre(z1 / z0)}
40 | ${t} s
41 | ${v.list[i].log}
42 | ${v.list[i].error_info}
43 | \n`
44 | }
45 |
46 | log = log + `
47 | 总计
48 | ${v.list.length }个文件
49 | ${filter_file_size(zs0)} (${zs0} B)
50 | ${filter_file_size(zs1)} (${zs1} B)
51 | 线程数:${v.thread}
52 | ${filter_file_size(zs1 - zs0)}(${zs1 - zs0} B)
53 | ${filter_file_size_pre(zs1 / zs0)}
54 | 累计:${ts} s
55 | 实际耗时: ${(v.time / 1000).toFixed(1)} s, 平均耗时:${(v.time / 1000 / v.list.length).toFixed(1)}s
56 |
57 | \n`
58 |
59 | log = log + "
";
60 |
61 |
62 | return log;
63 | }
64 |
65 |
66 | window.outLogHtml = function ()
67 | {
68 | let log = getLog();
69 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp"
70 | temp = path.join(temp, "limitPNG_temp", "Report-limitPNG.html");
71 |
72 |
73 | let html = `
74 |
75 |
76 |
77 |
78 | limitPNG 处理报告
79 |
80 |
81 |
82 |
117 | ${log}
118 |
119 |
120 | `;
121 | fs.writeFileSync(temp, html, 'utf8');
122 | shell.openExternal(temp);
123 |
124 | return html
125 | }
126 |
127 |
128 | var _out_log_try_z = 0;
129 | window.out_log_try = function ()
130 | {
131 | _out_log_try_z++;
132 | if (_out_log_try_z >= 2)
133 | {
134 | outLogHtml();
135 | _out_log_try_z = 0;
136 | } else
137 | {
138 |
139 | _out_log_try_z + 1;
140 | }
141 |
142 | }
143 |
144 | window.fileCompare = function ()
145 | {
146 | function sortList(list, sortBy)
147 | {
148 | return list.sort(function (a, b)
149 | {
150 | return a[sortBy] - b[sortBy];
151 | });
152 | }
153 |
154 | dialog.showOpenDialog(
155 | {title: "对比文件大小 - 第一个选中的被认为是原图", properties: ["openFile", "multiSelections"]},
156 | (files)=>
157 | {
158 | if (files != undefined && files.length > 0)
159 | {
160 |
161 | var log = `
162 | 排名
163 | 文件名
164 | 大小
165 | 减少大小
166 | 减少比例
167 | `;
168 |
169 | var orgSize = 0;
170 |
171 | var ranks = [];
172 |
173 | for (let i = 0; i < files.length; i++)
174 | {
175 | let stat = fs.statSync(files[i])
176 | let z0 = stat.size;
177 | if (i == 0)
178 | {
179 | orgSize = z0;
180 | log = log + `
181 | 原图
182 | ${path.basename(files[i])}
183 | ${filter_file_size(z0)} (${z0} B)
184 |
185 |
186 | \n`
187 | } else
188 | {
189 | ranks.push( {
190 | size: z0, html: `
191 | ${path.basename(files[i])}
192 | ${filter_file_size(z0)} (${z0} B)
193 | ${filter_file_size(orgSize - z0)} (${orgSize - z0} B)
194 | ${filter_file_size_pre(z0 / orgSize)}
195 | \n`
196 | })
197 | }
198 | }
199 | console.log(ranks);
200 | sortList(ranks, "size");
201 | console.log(ranks);
202 | for (let i = 0; i < ranks.length; i++)
203 | {
204 | if(i==0)
205 | {
206 | log = log + `${i + 1} `
207 | }else
208 | {
209 | log = log + `${i + 1} `
210 | }
211 |
212 | log = log + ranks[i].html;
213 | }
214 |
215 | log = log + "
";
216 | if(ranks.length>2)
217 | {
218 | log = log + ` 第一名比第二名减少 ${filter_file_size(ranks[1].size - ranks[0].size)}(${ranks[1].size - ranks[0].size} B), 减少比例:${filter_file_size_pre(ranks[0].size / ranks[1].size)}
`
219 |
220 | }
221 |
222 |
223 | //-----out
224 | let temp = os.tmpdir(); //"C:\Users\nullice\AppData\Local\Temp"
225 | temp = path.join(temp, "limitPNG_temp", "Compare-Report-limitPNG.html");
226 |
227 |
228 | let html = `
229 |
230 |
231 |
232 |
233 | limitPNG 文件比较
234 |
235 |
236 |
237 |
290 | ${log}
291 |
292 |
293 | `;
294 | fs.writeFileSync(temp, html, 'utf8');
295 | shell.openExternal(temp);
296 |
297 | return html
298 |
299 |
300 | }
301 | }
302 | )
303 | }
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by bgllj on 2016/5/25.
3 | */
4 | const webpack = require('webpack');
5 |
6 | module.exports = {
7 | entry: {
8 | main:'./index.js',
9 | vendor:['vue']
10 | },
11 | output: {
12 | path: './bin',
13 | filename: 'main.js'
14 | },
15 | target: 'electron-renderer',
16 |
17 | module:{
18 | loaders:[
19 | {test: /\.js$/, exclude: /node_modules/, loader: 'babel-loader', query: {presets: 'es2015',}},
20 | {test: /\.css$/, loader: 'style-loader!css-loader' },
21 | {test: /\.sass$/, loaders: ["style", "css", "sass"]},
22 | {test: /\.scss$/, loaders: ["style", "css", "sass"]},
23 | {test: /\.(png|jpg|jpeg)$/, loader: 'url?limit=8000&name=../bin/img/[name].[ext]'},
24 | {test: /\.vue$/, loader: 'vue'},
25 |
26 | ]},
27 | plugins: [
28 | new webpack.BannerPlugin("---------nullice--------Banner 注释"),
29 | new webpack.optimize.CommonsChunkPlugin('vendor', 'vendor.bundle.js'),
30 | new webpack.optimize.UglifyJsPlugin({compress: {warnings: false}})
31 | ],
32 | // devtool: 'eval-source-map'
33 | };
--------------------------------------------------------------------------------
/website/ICON.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/ICON.ico
--------------------------------------------------------------------------------
/website/assets/css/admin.css:
--------------------------------------------------------------------------------
1 | /**
2 | * admin.css
3 | */
4 |
5 |
6 | /*
7 | fixed-layout 固定头部和边栏布局
8 | */
9 |
10 | html,
11 | body {
12 | height: 100%;
13 | overflow: hidden;
14 | }
15 |
16 | ul {
17 | margin-top: 0;
18 | }
19 |
20 | .admin-icon-yellow {
21 | color: #ffbe40;
22 | }
23 |
24 | .admin-header {
25 | position: fixed;
26 | top: 0;
27 | left: 0;
28 | right: 0;
29 | z-index: 1500;
30 | font-size: 1.4rem;
31 | margin-bottom: 0;
32 | }
33 |
34 | .admin-header-list a:hover :after {
35 | content: none;
36 | }
37 |
38 | .admin-main {
39 | position: relative;
40 | height: 100%;
41 | padding-top: 51px;
42 | background: #f3f3f3;
43 | }
44 |
45 | .admin-menu {
46 | position: fixed;
47 | z-index: 10;
48 | bottom: 30px;
49 | right: 20px;
50 | }
51 |
52 | .admin-sidebar {
53 | width: 260px;
54 | min-height: 100%;
55 | float: left;
56 | border-right: 1px solid #cecece;
57 | }
58 |
59 | .admin-sidebar.am-active {
60 | z-index: 1600;
61 | }
62 |
63 | .admin-sidebar-list {
64 | margin-bottom: 0;
65 | }
66 |
67 | .admin-sidebar-list li a {
68 | color: #5c5c5c;
69 | padding-left: 24px;
70 | }
71 |
72 | .admin-sidebar-list li:first-child {
73 | border-top: none;
74 | }
75 |
76 | .admin-sidebar-sub {
77 | margin-top: 0;
78 | margin-bottom: 0;
79 | box-shadow: 0 16px 8px -15px #e2e2e2 inset;
80 | background: #ececec;
81 | padding-left: 24px;
82 | }
83 |
84 | .admin-sidebar-sub li:first-child {
85 | border-top: 1px solid #dedede;
86 | }
87 |
88 | .admin-sidebar-panel {
89 | margin: 10px;
90 | }
91 |
92 | .admin-content {
93 | display: -webkit-box;
94 | display: -webkit-flex;
95 | display: -ms-flexbox;
96 | display: flex;
97 | -webkit-box-orient: vertical;
98 | -webkit-box-direction: normal;
99 | -webkit-flex-direction: column;
100 | -ms-flex-direction: column;
101 | flex-direction: column;
102 | background: #fff;
103 | }
104 |
105 | .admin-content,
106 | .admin-sidebar {
107 | height: 100%;
108 | overflow-x: hidden;
109 | overflow-y: scroll;
110 | -webkit-overflow-scrolling: touch;
111 | }
112 |
113 | .admin-content-body {
114 | -webkit-box-flex: 1;
115 | -webkit-flex: 1 0 auto;
116 | -ms-flex: 1 0 auto;
117 | flex: 1 0 auto;
118 | }
119 |
120 | .admin-content-footer {
121 | font-size: 85%;
122 | color: #777;
123 | }
124 |
125 | .admin-content-list {
126 | border: 1px solid #e9ecf1;
127 | margin-top: 0;
128 | }
129 |
130 | .admin-content-list li {
131 | border: 1px solid #e9ecf1;
132 | border-width: 0 1px;
133 | margin-left: -1px;
134 | }
135 |
136 | .admin-content-list li:first-child {
137 | border-left: none;
138 | }
139 |
140 | .admin-content-list li:last-child {
141 | border-right: none;
142 | }
143 |
144 | .admin-content-table a {
145 | color: #535353;
146 | }
147 | .admin-content-file {
148 | margin-bottom: 0;
149 | color: #666;
150 | }
151 |
152 | .admin-content-file p {
153 | margin: 0 0 5px 0;
154 | font-size: 1.4rem;
155 | }
156 |
157 | .admin-content-file li {
158 | padding: 10px 0;
159 | }
160 |
161 | .admin-content-file li:first-child {
162 | border-top: none;
163 | }
164 |
165 | .admin-content-file li:last-child {
166 | border-bottom: none;
167 | }
168 |
169 | .admin-content-file li .am-progress {
170 | margin-bottom: 4px;
171 | }
172 |
173 | .admin-content-file li .am-progress-bar {
174 | line-height: 14px;
175 | }
176 |
177 | .admin-content-task {
178 | margin-bottom: 0;
179 | }
180 |
181 | .admin-content-task li {
182 | padding: 5px 0;
183 | border-color: #eee;
184 | }
185 |
186 | .admin-content-task li:first-child {
187 | border-top: none;
188 | }
189 |
190 | .admin-content-task li:last-child {
191 | border-bottom: none;
192 | }
193 |
194 | .admin-task-meta {
195 | font-size: 1.2rem;
196 | color: #999;
197 | }
198 |
199 | .admin-task-bd {
200 | font-size: 1.4rem;
201 | margin-bottom: 5px;
202 | }
203 |
204 | .admin-content-comment {
205 | margin-bottom: 0;
206 | }
207 |
208 | .admin-content-comment .am-comment-bd {
209 | font-size: 1.4rem;
210 | }
211 |
212 | .admin-content-pagination {
213 | margin-bottom: 0;
214 | }
215 | .admin-content-pagination li a {
216 | padding: 4px 8px;
217 | }
218 |
219 | @media only screen and (min-width: 641px) {
220 | .admin-sidebar {
221 | display: block;
222 | position: static;
223 | background: none;
224 | }
225 |
226 | .admin-offcanvas-bar {
227 | position: static;
228 | width: auto;
229 | background: none;
230 | -webkit-transform: translate3d(0, 0, 0);
231 | -ms-transform: translate3d(0, 0, 0);
232 | transform: translate3d(0, 0, 0);
233 | }
234 | .admin-offcanvas-bar:after {
235 | content: none;
236 | }
237 | }
238 |
239 | @media only screen and (max-width: 640px) {
240 | .admin-sidebar {
241 | width: inherit;
242 | }
243 |
244 | .admin-offcanvas-bar {
245 | background: #f3f3f3;
246 | }
247 |
248 | .admin-offcanvas-bar:after {
249 | background: #BABABA;
250 | }
251 |
252 | .admin-sidebar-list a:hover, .admin-sidebar-list a:active{
253 | -webkit-transition: background-color .3s ease;
254 | -moz-transition: background-color .3s ease;
255 | -ms-transition: background-color .3s ease;
256 | -o-transition: background-color .3s ease;
257 | transition: background-color .3s ease;
258 | background: #E4E4E4;
259 | }
260 |
261 | .admin-content-list li {
262 | padding: 10px;
263 | border-width: 1px 0;
264 | margin-top: -1px;
265 | }
266 |
267 | .admin-content-list li:first-child {
268 | border-top: none;
269 | }
270 |
271 | .admin-content-list li:last-child {
272 | border-bottom: none;
273 | }
274 |
275 | .admin-form-text {
276 | text-align: left !important;
277 | }
278 |
279 | }
280 |
281 | /*
282 | * user.html css
283 | */
284 | .user-info {
285 | margin-bottom: 15px;
286 | }
287 |
288 | .user-info .am-progress {
289 | margin-bottom: 4px;
290 | }
291 |
292 | .user-info p {
293 | margin: 5px;
294 | }
295 |
296 | .user-info-order {
297 | font-size: 1.4rem;
298 | }
299 |
300 | /*
301 | * errorLog.html css
302 | */
303 |
304 | .error-log .am-pre-scrollable {
305 | max-height: 40rem;
306 | }
307 |
308 | /*
309 | * table.html css
310 | */
311 |
312 | .table-main {
313 | font-size: 1.4rem;
314 | padding: .5rem;
315 | }
316 |
317 | .table-main button {
318 | background: #fff;
319 | }
320 |
321 | .table-check {
322 | width: 30px;
323 | }
324 |
325 | .table-id {
326 | width: 50px;
327 | }
328 |
329 | @media only screen and (max-width: 640px) {
330 | .table-select {
331 | margin-top: 10px;
332 | margin-left: 5px;
333 | }
334 | }
335 |
336 | /*
337 | gallery.html css
338 | */
339 |
340 | .gallery-list li {
341 | padding: 10px;
342 | }
343 |
344 | .gallery-list a {
345 | color: #666;
346 | }
347 |
348 | .gallery-list a:hover {
349 | color: #3bb4f2;
350 | }
351 |
352 | .gallery-title {
353 | margin-top: 6px;
354 | font-size: 1.4rem;
355 | }
356 |
357 | .gallery-desc {
358 | font-size: 1.2rem;
359 | margin-top: 4px;
360 | }
361 |
362 | /*
363 | 404.html css
364 | */
365 |
366 | .page-404 {
367 | background: #fff;
368 | border: none;
369 | width: 200px;
370 | margin: 0 auto;
371 | }
372 |
--------------------------------------------------------------------------------
/website/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* Write your styles */
--------------------------------------------------------------------------------
/website/assets/fonts/FontAwesome.otf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/FontAwesome.otf
--------------------------------------------------------------------------------
/website/assets/fonts/fontawesome-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.eot
--------------------------------------------------------------------------------
/website/assets/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/website/assets/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/website/assets/fonts/fontawesome-webfont.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/fonts/fontawesome-webfont.woff2
--------------------------------------------------------------------------------
/website/assets/i/app-icon72x72@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/app-icon72x72@2x.png
--------------------------------------------------------------------------------
/website/assets/i/examples/admin-chrome.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-chrome.png
--------------------------------------------------------------------------------
/website/assets/i/examples/admin-firefox.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-firefox.png
--------------------------------------------------------------------------------
/website/assets/i/examples/admin-ie.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-ie.png
--------------------------------------------------------------------------------
/website/assets/i/examples/admin-opera.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-opera.png
--------------------------------------------------------------------------------
/website/assets/i/examples/admin-safari.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/admin-safari.png
--------------------------------------------------------------------------------
/website/assets/i/examples/adminPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/adminPage.png
--------------------------------------------------------------------------------
/website/assets/i/examples/blogPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/blogPage.png
--------------------------------------------------------------------------------
/website/assets/i/examples/landing.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/landing.png
--------------------------------------------------------------------------------
/website/assets/i/examples/landingPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/landingPage.png
--------------------------------------------------------------------------------
/website/assets/i/examples/loginPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/loginPage.png
--------------------------------------------------------------------------------
/website/assets/i/examples/sidebarPage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/examples/sidebarPage.png
--------------------------------------------------------------------------------
/website/assets/i/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/favicon.png
--------------------------------------------------------------------------------
/website/assets/i/startup-640x1096.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/assets/i/startup-640x1096.png
--------------------------------------------------------------------------------
/website/assets/js/amazeui.widgets.helper.js:
--------------------------------------------------------------------------------
1 | /*! Amaze UI v2.7.0 ~ Handlebars helper | by Amaze UI Team | (c) 2016 AllMobilize, Inc. | Licensed under MIT | 2016-05-24T10:02:50+0800 */
2 | (function(undefined) {
3 | 'use strict';
4 |
5 | var registerIfCondHelper = function(hbs) {
6 | hbs.registerHelper('ifCond', function(v1, operator, v2, options) {
7 | switch (operator) {
8 | case '==':
9 | return (v1 == v2) ? options.fn(this) : options.inverse(this);
10 | break;
11 | case '===':
12 | return (v1 === v2) ? options.fn(this) : options.inverse(this);
13 | break;
14 | case '<':
15 | return (v1 < v2) ? options.fn(this) : options.inverse(this);
16 | break;
17 | case '<=':
18 | return (v1 <= v2) ? options.fn(this) : options.inverse(this);
19 | break;
20 | case '>':
21 | return (v1 > v2) ? options.fn(this) : options.inverse(this);
22 | break;
23 | case '>=':
24 | return (v1 >= v2) ? options.fn(this) : options.inverse(this);
25 | break;
26 | default:
27 | return options.inverse(this);
28 | break;
29 | }
30 | return options.inverse(this);
31 | });
32 | };
33 |
34 | if (typeof module !== 'undefined' && module.exports) {
35 | module.exports = registerIfCondHelper;
36 | }
37 |
38 | this.Handlebars && registerIfCondHelper(this.Handlebars);
39 | }).call(this);
40 |
41 | (function(undefined){
42 | 'use strict';
43 |
44 | var registerAMUIPartials = function(hbs) {
45 | hbs.registerPartial('accordion', "{{#this}}\n \n {{#each content}}\n \n \n {{{title}}}\n \n \n \n \n {{{content}}}\n
\n \n \n {{/each}}\n \n{{/this}}\n");
46 |
47 | hbs.registerPartial('divider', "{{#this}}\n \n{{/this}}\n");
48 |
49 | hbs.registerPartial('duoshuo', "{{#this}}\n \n{{/this}}");
50 |
51 | hbs.registerPartial('figure', "{{#this}}\n \n {{#if content.link}}{{/if}}\n\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition '==' 'top'}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/ifCond}}\n {{/if}}\n\n {{#if content.img}}\n \n {{/if}}\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition '==' 'bottom'}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/ifCond}}\n {{else}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/if}}\n\n {{#if content.link}} {{/if}}\n \n{{/this}}\n");
52 |
53 | hbs.registerPartial('footer', "{{#this}}\n \n\n \n{{/this}}\n");
54 |
55 | hbs.registerPartial('gallery', "{{#this}}\n \n {{#each content}}\n \n \n \n {{/each}}\n \n{{/this}}\n");
56 |
57 | hbs.registerPartial('gotop', "{{#this}}\n \n{{/this}}\n");
58 |
59 | hbs.registerPartial('header', "{{#this}}\n \n{{/this}}\n");
60 |
61 | hbs.registerPartial('intro', "{{#this }}\n \n {{#if content.title}}\n
\n
{{{content.title}}} \n {{#if content.more.link}}\n {{#ifCond options.position '==' 'top'}}\n
{{content.more.title}} \n {{/ifCond}}\n {{/if}}\n
\n {{/if}}\n\n
\n {{#if content.left}}\n
{{{content.left}}}
\n {{/if}}\n {{#if content.right}}\n
{{{content.right}}}
\n {{/if}}\n
\n {{#ifCond options.position '==' 'bottom'}}\n
\n {{/ifCond}}\n
\n{{/this}}\n");
62 |
63 | hbs.registerPartial('list_news', "{{#this}}\n \n \n {{#if content.header.title}}\n
\n {{#if content.header.link}} \n \n {{else}} \n
{{{content.header.title}}} \n {{/if}}\n \n {{/if}}\n\n
\n
\n {{#ifCond options.type '==' 'thumb'}}\n {{#ifCond options.thumbPosition '==' 'top'}} \n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if img}}\n \n
\n \n \n {{#if thumbAddition}}\n
{{{thumbAddition}}}
\n {{/if}}\n
\n {{/if}}\n\n \n {{#if title}}\n
\n {{/if}}\n\n {{#if date}}\n
{{date}} \n {{/if}}\n\n {{#if desc}}\n
{{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n
{{{mainAddition}}}
\n {{/if}}\n
\n \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition '==' 'bottom-left'}} \n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n \n {{/if}}\n {{#if img}}\n \n
\n \n \n {{#if thumbAddition}}\n
{{{thumbAddition}}}
\n {{/if}}\n
\n {{/if}}\n\n \n {{#if date}}\n
{{date}} \n {{/if}}\n\n {{#if desc}}\n
{{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n
{{{mainAddition}}}
\n {{/if}}\n
\n \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition '==' 'bottom-right'}} \n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n \n {{/if}}\n\n \n {{#if date}}\n
{{date}} \n {{/if}}\n\n {{#if desc}}\n
{{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n
{{{mainAddition}}}
\n {{/if}}\n
\n {{#if img}}\n \n
\n \n \n {{#if thumbAddition}}\n
{{{thumbAddition}}}
\n {{/if}}\n
\n {{/if}}\n \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition '==' 'left'}} \n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if img}}\n \n
\n \n \n {{#if thumbAddition}}\n
{{{thumbAddition}}}
\n {{/if}}\n
\n {{/if}}\n\n \n {{#if title}}\n
\n {{/if}}\n {{#if date}}\n
{{date}} \n {{/if}}\n\n {{#if desc}}\n
{{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n
{{{mainAddition}}}
\n {{/if}}\n
\n \n {{/each}}\n {{/ifCond}}\n\n {{#ifCond options.thumbPosition '==' 'right'}} \n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n \n {{#if title}}\n
\n {{/if}}\n\n {{#if date}}\n
{{date}} \n {{/if}}\n\n {{#if desc}}\n
{{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n
{{{mainAddition}}}
\n {{/if}}\n
\n {{#if img}}\n \n
\n \n \n {{#if thumbAddition}}\n
{{{thumbAddition}}}
\n {{/if}}\n
\n {{/if}}\n \n {{/each}}\n {{/ifCond}}\n\n {{else}}{{!--不带缩略图--}}\n {{#each content.main}}\n \n {{!--\n am-list-item-dated - 带日期\n am-list-item-desced - 带描述\n am-list-item-thumbed - 带缩略图的\n --}}\n {{#if title}}\n {{{title}}} \n {{/if}}\n\n {{#if date}}\n {{date}} \n {{/if}}\n\n {{#if desc}}\n {{{desc}}}
\n {{/if}}\n\n {{#if mainAddition}}\n {{{mainAddition}}}
\n {{/if}}\n \n {{/each}}\n {{/ifCond}}\n \n
\n\n {{#ifCond content.header.morePosition '==' 'bottom'}}\n {{#if content.header.link}}\n
\n {{/if}}\n {{/ifCond}}\n
\n{{/this}}\n");
64 |
65 | hbs.registerPartial('map', "{{#this}}\n \n{{/this}}");
66 |
67 | hbs.registerPartial('mechat', "{{#this}}\n \n{{/this}}");
68 |
69 | hbs.registerPartial('menu', "{{#this}}\n \n{{/this}}\n");
70 |
71 | hbs.registerPartial('navbar', "{{#this}}\n \n {{#if content}}\n
\n {{/if}}\n
\n{{/this}}\n");
72 |
73 | hbs.registerPartial('pagination', "{{#this}}\n \n{{/this}}\n");
74 |
75 | hbs.registerPartial('paragraph', "{{#this}}\n \n\n {{#if content}}\n {{{ content.content }}}\n {{/if}}\n \n{{/this}}\n");
76 |
77 | hbs.registerPartial('slider', "{{#this}}\n \n{{/this}}");
78 |
79 | hbs.registerPartial('tabs', "{{#this}}\n \n {{#if content}}\n
\n
\n {{#each content}}\n
\n {{{content}}}\n
\n {{/each}}\n
\n {{/if}}\n
\n{{/this}}\n");
80 |
81 | hbs.registerPartial('titlebar', "{{#this}}\n\n {{#if content.title}}\n
\n {{#if content.link}}\n {{{content.title}}} \n {{else}}\n {{{content.title}}}\n {{/if}}\n \n {{/if}}\n\n {{#if content.nav}}\n
\n {{#each content.nav}}\n {{{title}}} \n {{/each}}\n \n {{/if}}\n
\n{{/this}}\n");
82 |
83 | hbs.registerPartial('wechatpay', "{{#this}}\n \n \n {{#if content.title}}\n {{content.title}}\n {{else}}\n 微信支付\n {{/if}}\n \n
\n{{/this}}\n");
84 |
85 | };
86 |
87 | if (typeof module !== 'undefined' && module.exports) {
88 | module.exports = registerAMUIPartials;
89 | }
90 |
91 | this.Handlebars && registerAMUIPartials(this.Handlebars);
92 | }).call(this);
93 |
--------------------------------------------------------------------------------
/website/assets/js/amazeui.widgets.helper.min.js:
--------------------------------------------------------------------------------
1 | /*! Amaze UI v2.7.0 ~ Handlebars helper | by Amaze UI Team | (c) 2016 AllMobilize, Inc. | Licensed under MIT | 2016-05-24T10:02:50+0800 */
2 | (function(i){"use strict";var n=function(i){i.registerHelper("ifCond",function(i,n,t,a){switch(n){case"==":return i==t?a.fn(this):a.inverse(this);case"===":return i===t?a.fn(this):a.inverse(this);case"<":return t>i?a.fn(this):a.inverse(this);case"<=":return t>=i?a.fn(this):a.inverse(this);case">":return i>t?a.fn(this):a.inverse(this);case">=":return i>=t?a.fn(this):a.inverse(this);default:return a.inverse(this)}return a.inverse(this)})};"undefined"!=typeof module&&module.exports&&(module.exports=n),this.Handlebars&&n(this.Handlebars)}).call(this),function(i){"use strict";var n=function(i){i.registerPartial("accordion",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("divider",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("duoshuo",'{{#this}}\n \n{{/this}}'),i.registerPartial("figure",'{{#this}}\n \n {{#if content.link}}{{/if}}\n\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'top\'}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/ifCond}}\n {{/if}}\n\n {{#if content.img}}\n \n {{/if}}\n {{#if options.figcaptionPosition}}\n {{#ifCond options.figcaptionPosition \'==\' \'bottom\'}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/ifCond}}\n {{else}}\n {{#if content.figcaption}}\n \n {{content.figcaption}}\n \n {{/if}}\n {{/if}}\n\n {{#if content.link}} {{/if}}\n \n{{/this}}\n'),i.registerPartial("footer",'{{#this}}\n \n\n \n{{/this}}\n'),i.registerPartial("gallery",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("gotop",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("header",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("intro",'{{#this }}\n \n{{/this}}\n'),i.registerPartial("list_news",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("map",'{{#this}}\n \n{{/this}}'),i.registerPartial("mechat",'{{#this}}\n \n{{/this}}'),i.registerPartial("menu",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("navbar",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("pagination",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("paragraph",'{{#this}}\n \n\n {{#if content}}\n {{{ content.content }}}\n {{/if}}\n \n{{/this}}\n'),i.registerPartial("slider",'{{#this}}\n \n{{/this}}'),i.registerPartial("tabs",'{{#this}}\n \n{{/this}}\n'),i.registerPartial("titlebar",'{{#this}}\n\n{{/this}}\n'),
3 | i.registerPartial("wechatpay",'{{#this}}\n \n \n {{#if content.title}}\n {{content.title}}\n {{else}}\n 微信支付\n {{/if}}\n \n
\n{{/this}}\n')};"undefined"!=typeof module&&module.exports&&(module.exports=n),this.Handlebars&&n(this.Handlebars)}.call(this);
--------------------------------------------------------------------------------
/website/assets/js/app.js:
--------------------------------------------------------------------------------
1 | (function($) {
2 | 'use strict';
3 |
4 | $(function() {
5 | var $fullText = $('.admin-fullText');
6 | $('#admin-fullscreen').on('click', function() {
7 | $.AMUI.fullscreen.toggle();
8 | });
9 |
10 | $(document).on($.AMUI.fullscreen.raw.fullscreenchange, function() {
11 | $fullText.text($.AMUI.fullscreen.isFullscreen ? '退出全屏' : '开启全屏');
12 | });
13 | });
14 | })(jQuery);
15 |
--------------------------------------------------------------------------------
/website/css.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/css.css
--------------------------------------------------------------------------------
/website/img/1_lim[lossy-low].png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/1_lim[lossy-low].png
--------------------------------------------------------------------------------
/website/img/cpu.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/img/gif3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/gif3.gif
--------------------------------------------------------------------------------
/website/img/logo-mini.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/logo-mini.png
--------------------------------------------------------------------------------
/website/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/logo.png
--------------------------------------------------------------------------------
/website/img/lossy.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/img/no1.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/img/option.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/img/timecus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/timecus.png
--------------------------------------------------------------------------------
/website/img/top.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/website/img/vs/PNG_lossy_vs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/PNG_lossy_vs.png
--------------------------------------------------------------------------------
/website/img/vs/download.url:
--------------------------------------------------------------------------------
1 | [InternetShortcut]
2 | URL=data:text/mce-internal,content,%3Cimg%20id%3D%22__wp-temp-img-id%22%20src%3D%22http%3A//77we48.com1.z0.glb.clouddn.com/PNG_%25E5%25AF%25B9%25E6%25AF%2594_%25E7%25BD%2591%25E9%25A1%25B5%2520banner_1.png%22%20width%3D%22900%22%20height%3D%22300%22%20/%3E
3 |
--------------------------------------------------------------------------------
/website/img/vs/lossy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/lossy.png
--------------------------------------------------------------------------------
/website/img/vs/ss.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/ss.png
--------------------------------------------------------------------------------
/website/img/vs/vuelogo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nullice/limitPNG/83ac03f5d226c91c79c2e4cf9077ecd814d1582a/website/img/vs/vuelogo.png
--------------------------------------------------------------------------------
/website/index.css:
--------------------------------------------------------------------------------
1 | .get {
2 | background: #FFFFFF;
3 | color: #02BECA;
4 | text-align: center;
5 | padding: 40px 0;
6 | }
7 |
8 | .am-topbar {
9 | background: rgba(255, 255, 255, .23);
10 | border: none;
11 | color: #666;
12 | }
13 |
14 | .get-title {
15 | font-size: 200%;
16 | border: 2px solid #fff;
17 | padding: 10px;
18 | display: inline-block;
19 | margin: 0;
20 | }
21 |
22 | h1.get-title {
23 | font-weight: lighter;
24 | }
25 |
26 | h1.get-title+p {
27 | margin-top: 0;
28 | color: #90BBBB;
29 | font-weight: lighter;
30 | }
31 |
32 | .get-btn {
33 | background-color: #01ABC3;
34 | background: linear-gradient(70deg, #1ad7f9, #5eece4);
35 | border-radius: 100px;
36 | /* color: #fff; */
37 | }
38 |
39 |
40 | .get-btn:hover {
41 | transition: all .3s ease;
42 | margin-top: -2px;
43 | background: linear-gradient(40deg, #20cdff, #2dfff3);
44 | box-shadow: 0 10px 20px #54f3ff;
45 | /* border-radius: 0px; */
46 | }
47 |
48 | a.am-btn.am-btn-sm.get-btn {
49 | color: #fff;
50 | padding: 8px 40px;
51 | font-size: 15px;
52 | }
53 |
54 | .sur_info {
55 | font-size: 11px;
56 | color: #ADADAD;
57 | margin-top: -12px;
58 | }
59 |
60 | .detail {
61 | background: #fff;
62 | margin-top: 25px;
63 | }
64 |
65 | h2.detail-h2, .book-h2 {
66 | font-weight: 100;
67 | color: #004B50;
68 | text-align: left;
69 | margin-bottom: 10px;
70 | }
71 |
72 | .book-h2{
73 | color: #fff;
74 | }
75 |
76 | h2 span {
77 | color: rgb(171, 175, 175);
78 | font-size: 17px;
79 | padding-left: 10px;
80 | }
81 |
82 | .hope p{
83 | color: #fafbef;
84 | font-size: 14px;
85 | font-weight: lighter;
86 |
87 | }
88 |
89 |
90 | .book-h2 span{
91 | color: rgb(236, 242, 242);
92 | font-size: 17px;
93 | padding-left: 10px;
94 | }
95 |
96 | .detail p {
97 | color: #797979;
98 | margin-top: 10px;
99 | }
100 |
101 |
102 |
103 |
104 | tr.org, tr.org:hover {
105 | background-color: #61D1D4;
106 | color: #FFF;
107 | }
108 |
109 | tr.cp_no1 {
110 | background-color: rgba(77, 233, 234, 0.1);
111 | color: #20A8B7;
112 | }
113 |
114 | th {
115 | color: #A7A7A7;
116 | font-weight: lighter;
117 | }
118 |
119 | td, th {
120 | padding: 6px 9px;
121 | }
122 |
123 | table {
124 | margin: 20px;
125 | }
126 |
127 | tr {
128 | color: #989898;
129 | }
130 |
131 |
132 | table {
133 | margin-left: auto;
134 | margin-right: auto;
135 | }
136 |
137 |
138 | .am-figure-default figcaption {
139 | color: #949494;
140 | font-weight: lighter;
141 | }
142 |
143 |
144 |
145 | .am-topbar {
146 | font-weight: 100;
147 | }
148 |
149 | .am-topbar-brand {
150 |
151 | font-weight: normal;
152 | }
153 | .detail-h3 {
154 | /* color: #1f8dd6; */
155 | color: #004B50;
156 | font-weight: lighter;
157 | font-size: 17px;
158 | }
159 |
160 |
161 | ul.am-avg-sm-3.boxes {
162 | margin-top: 100px;
163 | }
164 |
165 | .about-title {
166 | font-weight: lighter;
167 | }
168 |
169 |
170 | .footer p {
171 | color: #7f8c8d;
172 | margin: 0;
173 | padding: 15px 0;
174 | text-align: center;
175 | background: #E0E0E0;
176 | }
177 |
178 | .detail.d2{
179 | margin-top: 20px;
180 | }
181 |
182 |
183 |
184 | .about {
185 | margin-top: 150px;
186 | }
187 |
188 | .am-topbar-brand img {
189 | margin-bottom: 8px;
190 | }
191 | h2.about-title.about-color {
192 | font-size: 25px;
193 | padding-bottom: 0px;
194 | margin-bottom: 5px;
195 | }
196 |
197 | .about-color+p {
198 | padding-top: 0px;
199 | margin-top: 5px;
200 | text-align: center;
201 | }
202 | a {
203 | color: #33A6BB;
204 | }
205 |
206 | .update {
207 | font-size: 10px;
208 | margin-top: 5px;
209 | padding: 5px;
210 | color: #A2A1A0;
211 | }
212 |
213 | .sur_info a:hover {
214 | color: #3BB4F2;
215 | }
216 | .sur_info a {
217 | color: #AAADAD;
218 | }
219 |
220 |
221 |
222 |
223 | .changelog{
224 |
225 | margin-top: 5px;
226 | display: none;
227 | }
228 |
229 | .update:hover .changelog{
230 |
231 | display: block;
232 | }
233 |
--------------------------------------------------------------------------------
/website/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | limitPNG | PNG 图片极限压缩工具
6 |
7 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | limitPNG
110 |
111 |
112 |
导航切换
115 |
116 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
如此简单的到达 PNG 无损压缩的极限
140 |
141 | 比其他同类压缩工具压缩的更小,支持无损压缩和有损压缩,可调多线程处理,丰富参数可选,并且这是免费的
142 |
143 |
144 | 下载
145 |
146 |
147 |
153 |
154 |
beta4 修复了无法清空列表的 bug;现在添加文件是追加到列表;启动速度提升;
155 |
beta3 修复了“无损-强力”模式无法正常使用和任务进行中可以拖入文件的 bug
156 |
157 |
158 |
如果需要压缩超大批量的图片,可以试一试能够保存压缩进度、不到 1.3 MB 的
gluttonyPNG
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
无损压缩 不改变任何像素的安全压缩
177 |
limitPNG 支持无损压缩和有损压缩两种压缩方式,其中无损压缩是不损失任何画质的压缩方法,与有损压缩相比(如 tinypng),虽然体积没优势,但是在对品质有要求的生产环境中不改变原图任何一个像素是必须的
178 |
179 |
而在无损压缩工具中,即使比起公认无损压缩率最高的工具: PNGGauntle 和 scriptPNG ,limitPNG 仍能压缩的比它们压缩的更小:
180 |
181 |
182 |
183 |
184 |
185 |
186 | 示例-网站图片
187 |
188 |
189 |
190 |
191 |
192 |
193 | 排名
194 | 文件名
195 | 大小
196 | 减少大小
197 | 减少比例
198 |
199 |
200 | 原图
201 | 网站图片-1.png
202 | 11.19 KB (11460 B)
203 |
204 |
205 |
206 |
207 | 1
208 | 网站图片-1 [limitPNG].png
209 | 7.71 KB (7898 B)
210 | 3.48 KB (3562 B)
211 | - 31%
212 |
213 |
214 | 2
215 | 网站图片-1_[scriptPNG].png
216 | 8.13 KB (8325 B)
217 | 3.06 KB (3135 B)
218 | - 27%
219 |
220 |
221 | 3
222 | 网站图片-1_[PNGGauntle].png
223 | 8.13 KB (8325 B)
224 | 3.06 KB (3135 B)
225 | - 27%
226 |
227 |
228 | 4
229 | 网站图片-1_[Caesium].png
230 | 14.96 KB (15319 B)
231 | -3859 B (-3859 B)
232 | + 33%
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 | 示例-网站图片2
245 |
246 |
247 |
248 |
249 |
250 |
251 | 排名
252 | 文件名
253 | 大小
254 | 减少大小
255 | 减少比例
256 |
257 |
258 | 原图
259 | 网页 banner_1.png
260 | 330.63 KB (338566 B)
261 |
262 |
263 |
264 |
265 | 1
266 | 网页 banner_1_[limitPNG].png
267 | 279.99 KB (286711 B)
268 | 50.64 KB (51855 B)
269 | - 15%
270 |
271 |
272 | 2
273 | 网页 banner_1_[PNGGauntle].png
274 | 282.76 KB (289547 B)
275 | 47.87 KB (49019 B)
276 | - 14%
277 |
278 |
279 | 3
280 | 网页 banner_1_[scriptPNG].png
281 | 287.16 KB (294053 B)
282 | 43.47 KB (44513 B)
283 | - 13%
284 |
285 |
286 | 4
287 | 网页 banner_1_[Caesium].png
288 | 389.40 KB (398746 B)
289 | -60180 B (-60180 B)
290 | + 17%
291 |
292 |
293 |
294 |
295 |
296 |
297 |
有损压缩 损失图片质量的暴力压缩
298 |
虽然有损压缩会损失画质,但是一些场合对画质要求并不高,而有损压缩能减少更多的体积。limitPNG 也支持有损压缩模式,并且相对于同类工具(tinypng、PP鸭、pngyu 等),limitPNG 有更多画质选项,可以更灵活的在画质和尺寸间取舍。
299 | 当然压缩效果也毫不逊色:
300 |
301 |
302 |
303 |
更多对比测试
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
压缩速度
314 |
315 | limitPNG 有“快速”、“强力”、“极限” 3 级压缩模式可选。极限压缩会花费大量时间,如果你没那个耐心可以选择“快速”模式。
316 | 快速模式通常已经能达到极限效果的 90%,而花费时间只要“极限”模式的 20 分之一甚至更少。
317 | 当然为了追求最后 10% 的压缩效果,多花一些时间也是值得的,而其中的取舍由你决定。
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
PNG-8 + Alpha 256 色的 PNG 也能支持透明通道
336 |
常用 Photoshop 的你应该会认为 256 色的 PNG-8 虽然体积小,但是却没有透明通道,透明边缘只能是生硬的锯齿,只好使用支持通道的 png-24 。
337 | 而使用 limitPNG 压缩的(模式:有损- 256 色)256 色 PNG 图片,可以拥有透明通道,让你不用为了要使用透明边缘而放弃体积更小的 PNG-8
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
并发处理 支持同时压缩多个图片
350 |
你可以根据实际情况设置同时压缩的任务数,比如当要处理大量小图时,设置更多的并发任务数可以大幅减少处理时间
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
这还是个测试中的软件,将来会变得更好
363 |
你可以在下面留言或者通过 微博 或 E-mail 来反馈
364 |
365 |
366 |
367 |
368 |
369 |
370 |
371 |
372 |
373 |
384 |
385 |
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 |
399 |
402 |
403 |
408 |
409 |
410 |
411 |
412 |
413 |
414 |
415 |
416 |
417 |
418 |
419 |
420 |
421 |
--------------------------------------------------------------------------------
/website/lib/jquery.event.move.js:
--------------------------------------------------------------------------------
1 | // jquery.event.move
2 | //
3 | // 1.3.6
4 | //
5 | // Stephen Band
6 | //
7 | // Triggers 'movestart', 'move' and 'moveend' events after
8 | // mousemoves following a mousedown cross a distance threshold,
9 | // similar to the native 'dragstart', 'drag' and 'dragend' events.
10 | // Move events are throttled to animation frames. Move event objects
11 | // have the properties:
12 | //
13 | // pageX:
14 | // pageY: Page coordinates of pointer.
15 | // startX:
16 | // startY: Page coordinates of pointer at movestart.
17 | // distX:
18 | // distY: Distance the pointer has moved since movestart.
19 | // deltaX:
20 | // deltaY: Distance the finger has moved since last event.
21 | // velocityX:
22 | // velocityY: Average velocity over last few events.
23 |
24 |
25 | (function (module) {
26 | if (typeof define === 'function' && define.amd) {
27 | // AMD. Register as an anonymous module.
28 | define(['jquery'], module);
29 | } else {
30 | // Browser globals
31 | module(jQuery);
32 | }
33 | })(function(jQuery, undefined){
34 |
35 | var // Number of pixels a pressed pointer travels before movestart
36 | // event is fired.
37 | threshold = 6,
38 |
39 | add = jQuery.event.add,
40 |
41 | remove = jQuery.event.remove,
42 |
43 | // Just sugar, so we can have arguments in the same order as
44 | // add and remove.
45 | trigger = function(node, type, data) {
46 | jQuery.event.trigger(type, data, node);
47 | },
48 |
49 | // Shim for requestAnimationFrame, falling back to timer. See:
50 | // see http://paulirish.com/2011/requestanimationframe-for-smart-animating/
51 | requestFrame = (function(){
52 | return (
53 | window.requestAnimationFrame ||
54 | window.webkitRequestAnimationFrame ||
55 | window.mozRequestAnimationFrame ||
56 | window.oRequestAnimationFrame ||
57 | window.msRequestAnimationFrame ||
58 | function(fn, element){
59 | return window.setTimeout(function(){
60 | fn();
61 | }, 25);
62 | }
63 | );
64 | })(),
65 |
66 | ignoreTags = {
67 | textarea: true,
68 | input: true,
69 | select: true,
70 | button: true
71 | },
72 |
73 | mouseevents = {
74 | move: 'mousemove',
75 | cancel: 'mouseup dragstart',
76 | end: 'mouseup'
77 | },
78 |
79 | touchevents = {
80 | move: 'touchmove',
81 | cancel: 'touchend',
82 | end: 'touchend'
83 | };
84 |
85 |
86 | // Constructors
87 |
88 | function Timer(fn){
89 | var callback = fn,
90 | active = false,
91 | running = false;
92 |
93 | function trigger(time) {
94 | if (active){
95 | callback();
96 | requestFrame(trigger);
97 | running = true;
98 | active = false;
99 | }
100 | else {
101 | running = false;
102 | }
103 | }
104 |
105 | this.kick = function(fn) {
106 | active = true;
107 | if (!running) { trigger(); }
108 | };
109 |
110 | this.end = function(fn) {
111 | var cb = callback;
112 |
113 | if (!fn) { return; }
114 |
115 | // If the timer is not running, simply call the end callback.
116 | if (!running) {
117 | fn();
118 | }
119 | // If the timer is running, and has been kicked lately, then
120 | // queue up the current callback and the end callback, otherwise
121 | // just the end callback.
122 | else {
123 | callback = active ?
124 | function(){ cb(); fn(); } :
125 | fn ;
126 |
127 | active = true;
128 | }
129 | };
130 | }
131 |
132 |
133 | // Functions
134 |
135 | function returnTrue() {
136 | return true;
137 | }
138 |
139 | function returnFalse() {
140 | return false;
141 | }
142 |
143 | function preventDefault(e) {
144 | e.preventDefault();
145 | }
146 |
147 | function preventIgnoreTags(e) {
148 | // Don't prevent interaction with form elements.
149 | if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
150 |
151 | e.preventDefault();
152 | }
153 |
154 | function isLeftButton(e) {
155 | // Ignore mousedowns on any button other than the left (or primary)
156 | // mouse button, or when a modifier key is pressed.
157 | return (e.which === 1 && !e.ctrlKey && !e.altKey);
158 | }
159 |
160 | function identifiedTouch(touchList, id) {
161 | var i, l;
162 |
163 | if (touchList.identifiedTouch) {
164 | return touchList.identifiedTouch(id);
165 | }
166 |
167 | // touchList.identifiedTouch() does not exist in
168 | // webkit yet… we must do the search ourselves...
169 |
170 | i = -1;
171 | l = touchList.length;
172 |
173 | while (++i < l) {
174 | if (touchList[i].identifier === id) {
175 | return touchList[i];
176 | }
177 | }
178 | }
179 |
180 | function changedTouch(e, event) {
181 | var touch = identifiedTouch(e.changedTouches, event.identifier);
182 |
183 | // This isn't the touch you're looking for.
184 | if (!touch) { return; }
185 |
186 | // Chrome Android (at least) includes touches that have not
187 | // changed in e.changedTouches. That's a bit annoying. Check
188 | // that this touch has changed.
189 | if (touch.pageX === event.pageX && touch.pageY === event.pageY) { return; }
190 |
191 | return touch;
192 | }
193 |
194 |
195 | // Handlers that decide when the first movestart is triggered
196 |
197 | function mousedown(e){
198 | var data;
199 |
200 | if (!isLeftButton(e)) { return; }
201 |
202 | data = {
203 | target: e.target,
204 | startX: e.pageX,
205 | startY: e.pageY,
206 | timeStamp: e.timeStamp
207 | };
208 |
209 | add(document, mouseevents.move, mousemove, data);
210 | add(document, mouseevents.cancel, mouseend, data);
211 | }
212 |
213 | function mousemove(e){
214 | var data = e.data;
215 |
216 | checkThreshold(e, data, e, removeMouse);
217 | }
218 |
219 | function mouseend(e) {
220 | removeMouse();
221 | }
222 |
223 | function removeMouse() {
224 | remove(document, mouseevents.move, mousemove);
225 | remove(document, mouseevents.cancel, mouseend);
226 | }
227 |
228 | function touchstart(e) {
229 | var touch, template;
230 |
231 | // Don't get in the way of interaction with form elements.
232 | if (ignoreTags[ e.target.tagName.toLowerCase() ]) { return; }
233 |
234 | touch = e.changedTouches[0];
235 |
236 | // iOS live updates the touch objects whereas Android gives us copies.
237 | // That means we can't trust the touchstart object to stay the same,
238 | // so we must copy the data. This object acts as a template for
239 | // movestart, move and moveend event objects.
240 | template = {
241 | target: touch.target,
242 | startX: touch.pageX,
243 | startY: touch.pageY,
244 | timeStamp: e.timeStamp,
245 | identifier: touch.identifier
246 | };
247 |
248 | // Use the touch identifier as a namespace, so that we can later
249 | // remove handlers pertaining only to this touch.
250 | add(document, touchevents.move + '.' + touch.identifier, touchmove, template);
251 | add(document, touchevents.cancel + '.' + touch.identifier, touchend, template);
252 | }
253 |
254 | function touchmove(e){
255 | var data = e.data,
256 | touch = changedTouch(e, data);
257 |
258 | if (!touch) { return; }
259 |
260 | checkThreshold(e, data, touch, removeTouch);
261 | }
262 |
263 | function touchend(e) {
264 | var template = e.data,
265 | touch = identifiedTouch(e.changedTouches, template.identifier);
266 |
267 | if (!touch) { return; }
268 |
269 | removeTouch(template.identifier);
270 | }
271 |
272 | function removeTouch(identifier) {
273 | remove(document, '.' + identifier, touchmove);
274 | remove(document, '.' + identifier, touchend);
275 | }
276 |
277 |
278 | // Logic for deciding when to trigger a movestart.
279 |
280 | function checkThreshold(e, template, touch, fn) {
281 | var distX = touch.pageX - template.startX,
282 | distY = touch.pageY - template.startY;
283 |
284 | // Do nothing if the threshold has not been crossed.
285 | if ((distX * distX) + (distY * distY) < (threshold * threshold)) { return; }
286 |
287 | triggerStart(e, template, touch, distX, distY, fn);
288 | }
289 |
290 | function handled() {
291 | // this._handled should return false once, and after return true.
292 | this._handled = returnTrue;
293 | return false;
294 | }
295 |
296 | function flagAsHandled(e) {
297 | e._handled();
298 | }
299 |
300 | function triggerStart(e, template, touch, distX, distY, fn) {
301 | var node = template.target,
302 | touches, time;
303 |
304 | touches = e.targetTouches;
305 | time = e.timeStamp - template.timeStamp;
306 |
307 | // Create a movestart object with some special properties that
308 | // are passed only to the movestart handlers.
309 | template.type = 'movestart';
310 | template.distX = distX;
311 | template.distY = distY;
312 | template.deltaX = distX;
313 | template.deltaY = distY;
314 | template.pageX = touch.pageX;
315 | template.pageY = touch.pageY;
316 | template.velocityX = distX / time;
317 | template.velocityY = distY / time;
318 | template.targetTouches = touches;
319 | template.finger = touches ?
320 | touches.length :
321 | 1 ;
322 |
323 | // The _handled method is fired to tell the default movestart
324 | // handler that one of the move events is bound.
325 | template._handled = handled;
326 |
327 | // Pass the touchmove event so it can be prevented if or when
328 | // movestart is handled.
329 | template._preventTouchmoveDefault = function() {
330 | e.preventDefault();
331 | };
332 |
333 | // Trigger the movestart event.
334 | trigger(template.target, template);
335 |
336 | // Unbind handlers that tracked the touch or mouse up till now.
337 | fn(template.identifier);
338 | }
339 |
340 |
341 | // Handlers that control what happens following a movestart
342 |
343 | function activeMousemove(e) {
344 | var timer = e.data.timer;
345 |
346 | e.data.touch = e;
347 | e.data.timeStamp = e.timeStamp;
348 | timer.kick();
349 | }
350 |
351 | function activeMouseend(e) {
352 | var event = e.data.event,
353 | timer = e.data.timer;
354 |
355 | removeActiveMouse();
356 |
357 | endEvent(event, timer, function() {
358 | // Unbind the click suppressor, waiting until after mouseup
359 | // has been handled.
360 | setTimeout(function(){
361 | remove(event.target, 'click', returnFalse);
362 | }, 0);
363 | });
364 | }
365 |
366 | function removeActiveMouse(event) {
367 | remove(document, mouseevents.move, activeMousemove);
368 | remove(document, mouseevents.end, activeMouseend);
369 | }
370 |
371 | function activeTouchmove(e) {
372 | var event = e.data.event,
373 | timer = e.data.timer,
374 | touch = changedTouch(e, event);
375 |
376 | if (!touch) { return; }
377 |
378 | // Stop the interface from gesturing
379 | e.preventDefault();
380 |
381 | event.targetTouches = e.targetTouches;
382 | e.data.touch = touch;
383 | e.data.timeStamp = e.timeStamp;
384 | timer.kick();
385 | }
386 |
387 | function activeTouchend(e) {
388 | var event = e.data.event,
389 | timer = e.data.timer,
390 | touch = identifiedTouch(e.changedTouches, event.identifier);
391 |
392 | // This isn't the touch you're looking for.
393 | if (!touch) { return; }
394 |
395 | removeActiveTouch(event);
396 | endEvent(event, timer);
397 | }
398 |
399 | function removeActiveTouch(event) {
400 | remove(document, '.' + event.identifier, activeTouchmove);
401 | remove(document, '.' + event.identifier, activeTouchend);
402 | }
403 |
404 |
405 | // Logic for triggering move and moveend events
406 |
407 | function updateEvent(event, touch, timeStamp, timer) {
408 | var time = timeStamp - event.timeStamp;
409 |
410 | event.type = 'move';
411 | event.distX = touch.pageX - event.startX;
412 | event.distY = touch.pageY - event.startY;
413 | event.deltaX = touch.pageX - event.pageX;
414 | event.deltaY = touch.pageY - event.pageY;
415 |
416 | // Average the velocity of the last few events using a decay
417 | // curve to even out spurious jumps in values.
418 | event.velocityX = 0.3 * event.velocityX + 0.7 * event.deltaX / time;
419 | event.velocityY = 0.3 * event.velocityY + 0.7 * event.deltaY / time;
420 | event.pageX = touch.pageX;
421 | event.pageY = touch.pageY;
422 | }
423 |
424 | function endEvent(event, timer, fn) {
425 | timer.end(function(){
426 | event.type = 'moveend';
427 |
428 | trigger(event.target, event);
429 |
430 | return fn && fn();
431 | });
432 | }
433 |
434 |
435 | // jQuery special event definition
436 |
437 | function setup(data, namespaces, eventHandle) {
438 | // Stop the node from being dragged
439 | //add(this, 'dragstart.move drag.move', preventDefault);
440 |
441 | // Prevent text selection and touch interface scrolling
442 | //add(this, 'mousedown.move', preventIgnoreTags);
443 |
444 | // Tell movestart default handler that we've handled this
445 | add(this, 'movestart.move', flagAsHandled);
446 |
447 | // Don't bind to the DOM. For speed.
448 | return true;
449 | }
450 |
451 | function teardown(namespaces) {
452 | remove(this, 'dragstart drag', preventDefault);
453 | remove(this, 'mousedown touchstart', preventIgnoreTags);
454 | remove(this, 'movestart', flagAsHandled);
455 |
456 | // Don't bind to the DOM. For speed.
457 | return true;
458 | }
459 |
460 | function addMethod(handleObj) {
461 | // We're not interested in preventing defaults for handlers that
462 | // come from internal move or moveend bindings
463 | if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
464 | return;
465 | }
466 |
467 | // Stop the node from being dragged
468 | add(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid, preventDefault, undefined, handleObj.selector);
469 |
470 | // Prevent text selection and touch interface scrolling
471 | add(this, 'mousedown.' + handleObj.guid, preventIgnoreTags, undefined, handleObj.selector);
472 | }
473 |
474 | function removeMethod(handleObj) {
475 | if (handleObj.namespace === "move" || handleObj.namespace === "moveend") {
476 | return;
477 | }
478 |
479 | remove(this, 'dragstart.' + handleObj.guid + ' drag.' + handleObj.guid);
480 | remove(this, 'mousedown.' + handleObj.guid);
481 | }
482 |
483 | jQuery.event.special.movestart = {
484 | setup: setup,
485 | teardown: teardown,
486 | add: addMethod,
487 | remove: removeMethod,
488 |
489 | _default: function(e) {
490 | var event, data;
491 |
492 | // If no move events were bound to any ancestors of this
493 | // target, high tail it out of here.
494 | if (!e._handled()) { return; }
495 |
496 | function update(time) {
497 | updateEvent(event, data.touch, data.timeStamp);
498 | trigger(e.target, event);
499 | }
500 |
501 | event = {
502 | target: e.target,
503 | startX: e.startX,
504 | startY: e.startY,
505 | pageX: e.pageX,
506 | pageY: e.pageY,
507 | distX: e.distX,
508 | distY: e.distY,
509 | deltaX: e.deltaX,
510 | deltaY: e.deltaY,
511 | velocityX: e.velocityX,
512 | velocityY: e.velocityY,
513 | timeStamp: e.timeStamp,
514 | identifier: e.identifier,
515 | targetTouches: e.targetTouches,
516 | finger: e.finger
517 | };
518 |
519 | data = {
520 | event: event,
521 | timer: new Timer(update),
522 | touch: undefined,
523 | timeStamp: undefined
524 | };
525 |
526 | if (e.identifier === undefined) {
527 | // We're dealing with a mouse
528 | // Stop clicks from propagating during a move
529 | add(e.target, 'click', returnFalse);
530 | add(document, mouseevents.move, activeMousemove, data);
531 | add(document, mouseevents.end, activeMouseend, data);
532 | }
533 | else {
534 | // We're dealing with a touch. Stop touchmove doing
535 | // anything defaulty.
536 | e._preventTouchmoveDefault();
537 | add(document, touchevents.move + '.' + e.identifier, activeTouchmove, data);
538 | add(document, touchevents.end + '.' + e.identifier, activeTouchend, data);
539 | }
540 | }
541 | };
542 |
543 | jQuery.event.special.move = {
544 | setup: function() {
545 | // Bind a noop to movestart. Why? It's the movestart
546 | // setup that decides whether other move events are fired.
547 | add(this, 'movestart.move', jQuery.noop);
548 | },
549 |
550 | teardown: function() {
551 | remove(this, 'movestart.move', jQuery.noop);
552 | }
553 | };
554 |
555 | jQuery.event.special.moveend = {
556 | setup: function() {
557 | // Bind a noop to movestart. Why? It's the movestart
558 | // setup that decides whether other move events are fired.
559 | add(this, 'movestart.moveend', jQuery.noop);
560 | },
561 |
562 | teardown: function() {
563 | remove(this, 'movestart.moveend', jQuery.noop);
564 | }
565 | };
566 |
567 | add(document, 'mousedown.move', mousedown);
568 | add(document, 'touchstart.move', touchstart);
569 |
570 | // Make jQuery copy touch event properties over to the jQuery event
571 | // object, if they are not already listed. But only do the ones we
572 | // really need. IE7/8 do not have Array#indexOf(), but nor do they
573 | // have touch events, so let's assume we can ignore them.
574 | if (typeof Array.prototype.indexOf === 'function') {
575 | (function(jQuery, undefined){
576 | var props = ["changedTouches", "targetTouches"],
577 | l = props.length;
578 |
579 | while (l--) {
580 | if (jQuery.event.props.indexOf(props[l]) === -1) {
581 | jQuery.event.props.push(props[l]);
582 | }
583 | }
584 | })(jQuery);
585 | };
586 | });
587 |
--------------------------------------------------------------------------------
/website/lib/jquery.twentytwenty.js:
--------------------------------------------------------------------------------
1 | (function($){
2 |
3 | $.fn.twentytwenty = function(options) {
4 | var options = $.extend({default_offset_pct: 0.5, orientation: 'horizontal'}, options);
5 | return this.each(function() {
6 |
7 | var sliderPct = options.default_offset_pct;
8 | var container = $(this);
9 | var sliderOrientation = options.orientation;
10 | var beforeDirection = (sliderOrientation === 'vertical') ? 'down' : 'left';
11 | var afterDirection = (sliderOrientation === 'vertical') ? 'up' : 'right';
12 |
13 |
14 | container.wrap("
");
15 | container.append("
");
16 | var beforeImg = container.find("img:first");
17 | var afterImg = container.find("img:last");
18 | container.append("
");
19 | var slider = container.find(".twentytwenty-handle");
20 | slider.append(" ");
21 | slider.append(" ");
22 | container.addClass("twentytwenty-container");
23 | beforeImg.addClass("twentytwenty-before");
24 | afterImg.addClass("twentytwenty-after");
25 |
26 | var overlay = container.find(".twentytwenty-overlay");
27 | overlay.append("
");
28 | overlay.append("
");
29 |
30 | var calcOffset = function(dimensionPct) {
31 | var w = beforeImg.width();
32 | var h = beforeImg.height();
33 | return {
34 | w: w+"px",
35 | h: h+"px",
36 | cw: (dimensionPct*w)+"px",
37 | ch: (dimensionPct*h)+"px"
38 | };
39 | };
40 |
41 | var adjustContainer = function(offset) {
42 | if (sliderOrientation === 'vertical') {
43 | beforeImg.css("clip", "rect(0,"+offset.w+","+offset.ch+",0)");
44 | }
45 | else {
46 | beforeImg.css("clip", "rect(0,"+offset.cw+","+offset.h+",0)");
47 | }
48 | container.css("height", offset.h);
49 | };
50 |
51 | var adjustSlider = function(pct) {
52 | var offset = calcOffset(pct);
53 | slider.css((sliderOrientation==="vertical") ? "top" : "left", (sliderOrientation==="vertical") ? offset.ch : offset.cw);
54 | adjustContainer(offset);
55 | }
56 |
57 | $(window).on("resize.twentytwenty", function(e) {
58 | adjustSlider(sliderPct);
59 | });
60 |
61 | var offsetX = 0;
62 | var imgWidth = 0;
63 |
64 | slider.on("movestart", function(e) {
65 | if (((e.distX > e.distY && e.distX < -e.distY) || (e.distX < e.distY && e.distX > -e.distY)) && sliderOrientation !== 'vertical') {
66 | e.preventDefault();
67 | }
68 | else if (((e.distX < e.distY && e.distX < -e.distY) || (e.distX > e.distY && e.distX > -e.distY)) && sliderOrientation === 'vertical') {
69 | e.preventDefault();
70 | }
71 | container.addClass("active");
72 | offsetX = container.offset().left;
73 | offsetY = container.offset().top;
74 | imgWidth = beforeImg.width();
75 | imgHeight = beforeImg.height();
76 | });
77 |
78 | slider.on("moveend", function(e) {
79 | container.removeClass("active");
80 | });
81 |
82 | slider.on("move", function(e) {
83 | if (container.hasClass("active")) {
84 | sliderPct = (sliderOrientation === 'vertical') ? (e.pageY-offsetY)/imgHeight : (e.pageX-offsetX)/imgWidth;
85 | if (sliderPct < 0) {
86 | sliderPct = 0;
87 | }
88 | if (sliderPct > 1) {
89 | sliderPct = 1;
90 | }
91 | adjustSlider(sliderPct);
92 | }
93 | });
94 |
95 | container.find("img").on("mousedown", function(event) {
96 | event.preventDefault();
97 | });
98 |
99 | $(window).trigger("resize.twentytwenty");
100 | });
101 | };
102 |
103 | })(jQuery);
104 |
--------------------------------------------------------------------------------
/website/lib/twentytwenty.css:
--------------------------------------------------------------------------------
1 | .twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after, .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
2 | content: " ";
3 | display: block;
4 | background: white;
5 | position: absolute;
6 | z-index: 30;
7 | -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
8 | -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
9 | box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5); }
10 |
11 | .twentytwenty-horizontal .twentytwenty-handle:before, .twentytwenty-horizontal .twentytwenty-handle:after {
12 | width: 3px;
13 | height: 9999px;
14 | left: 50%;
15 | margin-left: -1.5px; }
16 |
17 | .twentytwenty-vertical .twentytwenty-handle:before, .twentytwenty-vertical .twentytwenty-handle:after {
18 | width: 9999px;
19 | height: 3px;
20 | top: 50%;
21 | margin-top: -1.5px; }
22 |
23 | .twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
24 | position: absolute;
25 | top: 0;
26 | width: 100%;
27 | height: 100%; }
28 |
29 | .twentytwenty-before-label, .twentytwenty-after-label, .twentytwenty-overlay {
30 | -webkit-transition-duration: 0.5s;
31 | -moz-transition-duration: 0.5s;
32 | transition-duration: 0.5s; }
33 |
34 | .twentytwenty-before-label, .twentytwenty-after-label {
35 | -webkit-transition-property: opacity;
36 | -moz-transition-property: opacity;
37 | transition-property: opacity; }
38 |
39 | .twentytwenty-before-label:before, .twentytwenty-after-label:before {
40 | color: white;
41 | font-size: 13px;
42 | letter-spacing: 0.1em; }
43 |
44 | .twentytwenty-before-label:before, .twentytwenty-after-label:before {
45 | position: absolute;
46 | background: rgba(255, 255, 255, 0.2);
47 | line-height: 38px;
48 | padding: 0 20px;
49 | -webkit-border-radius: 2px;
50 | -moz-border-radius: 2px;
51 | border-radius: 2px; }
52 |
53 | .twentytwenty-horizontal .twentytwenty-before-label:before, .twentytwenty-horizontal .twentytwenty-after-label:before {
54 | top: 50%;
55 | margin-top: -19px; }
56 |
57 | .twentytwenty-vertical .twentytwenty-before-label:before, .twentytwenty-vertical .twentytwenty-after-label:before {
58 | left: 50%;
59 | margin-left: -45px;
60 | text-align: center;
61 | width: 90px; }
62 |
63 | .twentytwenty-left-arrow, .twentytwenty-right-arrow, .twentytwenty-up-arrow, .twentytwenty-down-arrow {
64 | width: 0;
65 | height: 0;
66 | border: 6px inset transparent;
67 | position: absolute; }
68 |
69 | .twentytwenty-left-arrow, .twentytwenty-right-arrow {
70 | top: 50%;
71 | margin-top: -6px; }
72 |
73 | .twentytwenty-up-arrow, .twentytwenty-down-arrow {
74 | left: 50%;
75 | margin-left: -6px; }
76 |
77 | .twentytwenty-container {
78 | -webkit-box-sizing: content-box;
79 | -moz-box-sizing: content-box;
80 | box-sizing: content-box;
81 | z-index: 0;
82 | overflow: hidden;
83 | position: relative;
84 | -webkit-user-select: none;
85 | -moz-user-select: none; }
86 | .twentytwenty-container img {
87 | max-width: 100%;
88 | position: absolute;
89 | top: 0;
90 | display: block; }
91 | .twentytwenty-container.active .twentytwenty-overlay, .twentytwenty-container.active :hover.twentytwenty-overlay {
92 | background: rgba(0, 0, 0, 0); }
93 | .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-before-label,
94 | .twentytwenty-container.active .twentytwenty-overlay .twentytwenty-after-label, .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-before-label,
95 | .twentytwenty-container.active :hover.twentytwenty-overlay .twentytwenty-after-label {
96 | opacity: 0; }
97 | .twentytwenty-container * {
98 | -webkit-box-sizing: content-box;
99 | -moz-box-sizing: content-box;
100 | box-sizing: content-box; }
101 |
102 | .twentytwenty-before-label {
103 | opacity: 0; }
104 | .twentytwenty-before-label:before {
105 | content: "Before"; }
106 |
107 | .twentytwenty-after-label {
108 | opacity: 0; }
109 | .twentytwenty-after-label:before {
110 | content: "After"; }
111 |
112 | .twentytwenty-horizontal .twentytwenty-before-label:before {
113 | left: 10px; }
114 |
115 | .twentytwenty-horizontal .twentytwenty-after-label:before {
116 | right: 10px; }
117 |
118 | .twentytwenty-vertical .twentytwenty-before-label:before {
119 | top: 10px; }
120 |
121 | .twentytwenty-vertical .twentytwenty-after-label:before {
122 | bottom: 10px; }
123 |
124 | .twentytwenty-overlay {
125 | -webkit-transition-property: background;
126 | -moz-transition-property: background;
127 | transition-property: background;
128 | background: rgba(0, 0, 0, 0);
129 | z-index: 25; }
130 | .twentytwenty-overlay:hover {
131 | background: rgba(0, 0, 0, 0.5); }
132 | .twentytwenty-overlay:hover .twentytwenty-after-label {
133 | opacity: 1; }
134 | .twentytwenty-overlay:hover .twentytwenty-before-label {
135 | opacity: 1; }
136 |
137 | .twentytwenty-before {
138 | z-index: 20; }
139 |
140 | .twentytwenty-after {
141 | z-index: 10; }
142 |
143 | .twentytwenty-handle {
144 | height: 38px;
145 | width: 38px;
146 | position: absolute;
147 | left: 50%;
148 | top: 50%;
149 | margin-left: -22px;
150 | margin-top: -22px;
151 | border: 3px solid white;
152 | -webkit-border-radius: 1000px;
153 | -moz-border-radius: 1000px;
154 | border-radius: 1000px;
155 | -webkit-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
156 | -moz-box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
157 | box-shadow: 0px 0px 12px rgba(51, 51, 51, 0.5);
158 | z-index: 40;
159 | cursor: pointer; }
160 |
161 | .twentytwenty-horizontal .twentytwenty-handle:before {
162 | bottom: 50%;
163 | margin-bottom: 22px;
164 | -webkit-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
165 | -moz-box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
166 | box-shadow: 0 3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
167 | .twentytwenty-horizontal .twentytwenty-handle:after {
168 | top: 50%;
169 | margin-top: 22px;
170 | -webkit-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
171 | -moz-box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
172 | box-shadow: 0 -3px 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
173 |
174 | .twentytwenty-vertical .twentytwenty-handle:before {
175 | left: 50%;
176 | margin-left: 22px;
177 | -webkit-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
178 | -moz-box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
179 | box-shadow: 3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
180 | .twentytwenty-vertical .twentytwenty-handle:after {
181 | right: 50%;
182 | margin-right: 22px;
183 | -webkit-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
184 | -moz-box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5);
185 | box-shadow: -3px 0 0 white, 0px 0px 12px rgba(51, 51, 51, 0.5); }
186 |
187 | .twentytwenty-left-arrow {
188 | border-right: 6px solid white;
189 | left: 50%;
190 | margin-left: -17px; }
191 |
192 | .twentytwenty-right-arrow {
193 | border-left: 6px solid white;
194 | right: 50%;
195 | margin-right: -17px; }
196 |
197 | .twentytwenty-up-arrow {
198 | border-bottom: 6px solid white;
199 | top: 50%;
200 | margin-top: -17px; }
201 |
202 | .twentytwenty-down-arrow {
203 | border-top: 6px solid white;
204 | bottom: 50%;
205 | margin-bottom: -17px; }
206 |
--------------------------------------------------------------------------------
/website/rrr.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Title
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------