├── .gitignore
├── example
├── images
│ ├── sq-icon.png
│ ├── sq-logo.jpg
│ ├── gallery-1.jpg
│ ├── gallery-2.jpg
│ ├── gallery-3.jpg
│ └── pic-home.jpg
├── css
│ ├── example-theme.css
│ ├── example-material.css
│ └── example.css
├── data
│ ├── list.json
│ └── loadmore
│ │ ├── 0
│ │ └── list.json
│ │ ├── 1
│ │ └── list.json
│ │ ├── 2
│ │ └── list.json
│ │ ├── 3
│ │ └── list.json
│ │ └── 4
│ │ └── list.json
├── js
│ └── example.js
├── example.html
├── column.html
├── panel.html
├── badge.html
├── slider.html
├── core-function.html
├── modal.html
├── grid.html
├── tab.html
├── dropdown.html
├── gallery.html
├── screenshot.html
├── index.html
├── bar.html
├── sticky.html
├── button.html
├── material.html
├── lazyload.html
├── loadmore.html
└── animate.html
├── dist
└── fonts
│ ├── fontawesome-webfont.ttf
│ └── fontawesome-webfont.woff
├── src
├── scripts
│ ├── squirrel.js
│ ├── core
│ │ ├── util.js
│ │ ├── ua.js
│ │ ├── core.js
│ │ └── store.js
│ ├── dropdown.js
│ ├── lazyload.js
│ ├── sticky.js
│ └── suggest.js
└── styles
│ ├── utilities.less
│ ├── plugins
│ ├── panel.less
│ ├── tabs.less
│ ├── slider.less
│ ├── modal.less
│ ├── loadmore.less
│ ├── dropdown.less
│ └── screenshot.less
│ ├── badge.less
│ ├── gallery.less
│ ├── column.less
│ ├── squirrel.less
│ ├── base.less
│ ├── mixins.less
│ ├── button.less
│ ├── grid.less
│ ├── themes
│ └── default-theme.less
│ ├── list.less
│ ├── bar.less
│ ├── form.less
│ ├── variables.less
│ └── normalize.less
├── .jshintrc
├── package.json
├── LICENSE.md
├── README.md
├── doc_tpl
├── documentation.html
└── index.html
└── gulpfile.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea/*
2 | /node_modules/*
3 | /bower_components/*
--------------------------------------------------------------------------------
/example/images/sq-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/sq-icon.png
--------------------------------------------------------------------------------
/example/images/sq-logo.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/sq-logo.jpg
--------------------------------------------------------------------------------
/example/images/gallery-1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-1.jpg
--------------------------------------------------------------------------------
/example/images/gallery-2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-2.jpg
--------------------------------------------------------------------------------
/example/images/gallery-3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/gallery-3.jpg
--------------------------------------------------------------------------------
/example/images/pic-home.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/example/images/pic-home.jpg
--------------------------------------------------------------------------------
/dist/fonts/fontawesome-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/dist/fonts/fontawesome-webfont.ttf
--------------------------------------------------------------------------------
/dist/fonts/fontawesome-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iinterest/Squirrel-3/HEAD/dist/fonts/fontawesome-webfont.woff
--------------------------------------------------------------------------------
/src/scripts/squirrel.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by Bell on 12/20/14.
3 | */
4 |
5 | $(function() {
6 | 'use strict';
7 | window.FastClick.attach(document.body);
8 | //alert('hehe')
9 | });
--------------------------------------------------------------------------------
/example/css/example-theme.css:
--------------------------------------------------------------------------------
1 | /*theme example*/
2 | .sq-bar{ background:#38c483; color:#fff; z-index:101;}
3 | .sq-bar a{ color:#fff;}
4 |
5 | /*.sq-list .list-item,*/
6 | /*.sq-list .list-item{ border-top-color:#e5e5e5; border-bottom-color:#e5e5e5;}*/
7 | .sq-list.text .sq-icon{ color:#999;}
8 |
9 | .sq-list .icon{ width:60px; height:60px;}
10 |
11 |
--------------------------------------------------------------------------------
/src/styles/utilities.less:
--------------------------------------------------------------------------------
1 | //
2 | // Utilities
3 | // 通用功能样式
4 | // ---------------------------------------------------------------------------
5 |
6 | .@{ns}fl{ float:left;}
7 | .@{ns}fr{ float:right;}
8 |
9 | // 显示
10 | .@{ns}hide{ display:none;}
11 | .@{ns}unvisible{ visibility:hidden;}
12 | .@{ns}clearfix{ .clearfix();}
13 |
14 | // 文字排版
15 | .@{ns}a-line{ .text-hide();}
16 | .@{ns}a-line-ellipsis{ .text-hide-ellipsis();}
17 |
--------------------------------------------------------------------------------
/example/css/example-material.css:
--------------------------------------------------------------------------------
1 | .mb--1{ margin-bottom:-1px !important;}
2 |
3 | /*common*/
4 | .example{ padding:0 10px 40px;}
5 | .example-title{ position:relative; margin:2em 0 .5em; padding:.5em; background:rgba(255, 255, 255, .8); border-bottom:1px solid #3f51b5; font-size:18px; color:#3f51b5; z-index:1; clear:both;}
6 | .example-title:first-of-type{ margin-top:.5em;}
7 | .example-row{ padding:5px;}
8 | .example hr{ margin:20px 0; border:none; border-bottom:1px dashed #eee;}
--------------------------------------------------------------------------------
/src/styles/plugins/panel.less:
--------------------------------------------------------------------------------
1 | //
2 | // Panel
3 | // @desc: 下拉菜单,panel.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}panel{ background:@panel-bg;}
7 |
8 | // 动画类,为了适应不同宽度的 panel 动画,具体动画样式写在了 panel.js 中。
9 | .animate-showPanel{ animation-name:showPanel;}
10 | .animate-hidePanel{ animation-name:hidePanel;}
11 | .animate-hideWrap{ animation-name:hideWrap;}
12 | .animate-showWrap{ animation-name:showWrap;}
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "bitwise": true,
3 | "camelcase": true,
4 | "curly": true,
5 | "eqeqeq": true,
6 | "forin": true,
7 | "immed": true,
8 | "indent": 4,
9 | "latedef": false,
10 | "newcap": true,
11 | "noarg": true,
12 | "noempty": true,
13 | "nonew": true,
14 | "quotmark": "single",
15 | "undef": true,
16 | //"unused": true,
17 | "strict": true,
18 | "trailing": true,
19 | "eqnull": true,
20 | "browser": true,
21 | "devel": true,
22 | "jquery": true,
23 | "node": true,
24 | "predef": ["SQ", "seajs", "define"],
25 | "white": false
26 | }
--------------------------------------------------------------------------------
/src/styles/badge.less:
--------------------------------------------------------------------------------
1 | //
2 | // Badge
3 | // @desc: 徽章
4 | // ---------------------------------------------------------------------------
5 |
6 | .@{ns}badge{
7 | border-radius:@badge-border-radius; padding:@badge-padding; font-weight:normal; font-size:@badge-font-size; overflow:hidden;
8 | &:empty{ display:none;}
9 | &.black{ background:@badge-black; color:@white;}
10 | &.blue{ background:@badge-blue; color:@white;}
11 | &.green{ background:@badge-green; color:@white;}
12 | &.grey{ background:@badge-grey; color:@white;}
13 | &.red{ background:@badge-red; color:@white;}
14 | &.orange{ background:@badge-orange; color:@white;}
15 | }
--------------------------------------------------------------------------------
/example/data/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/src/styles/plugins/tabs.less:
--------------------------------------------------------------------------------
1 | //
2 | // Tab
3 | // @desc: 下拉菜单,tab.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}nav-tabs{
7 | height:39px; border-bottom:1px solid #d7d7d7;
8 | & > li{
9 | min-width:40px; height:40px; padding:0 10px; border-bottom:3px solid transparent; line-height:40px; text-align:center; cursor:pointer;
10 | &.active{ border-bottom-color:#d7d7d7;}
11 | }
12 | }
13 | .@{ns}tab-content{
14 | display:none; border:1px solid transparent; min-height:100px;
15 | &.active{ display:block;}
16 | }
17 | .@{ns}tabs-loading-tip{
18 | padding:30px 0; text-align:center;
19 | .reload{
20 | &>p{ margin-bottom:.8em;}
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/example/data/loadmore/0/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 900,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/example/data/loadmore/1/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/example/data/loadmore/2/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/example/data/loadmore/3/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/example/data/loadmore/4/list.json:
--------------------------------------------------------------------------------
1 | {
2 | "code": 200,
3 | "data": [
4 | {"title":"Loadmore 内容1", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
5 | {"title":"Loadmore 内容2", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
6 | {"title":"Loadmore 内容3", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
7 | {"title":"Loadmore 内容4", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
8 | {"title":"Loadmore 内容5", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
9 | {"title":"Loadmore 内容6", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"},
10 | {"title":"Loadmore 内容7", "desc":"知识总是从爱好开始,犹如光总是从火开始一样。", "icon":"images/sq-logo.jpg"}
11 | ]
12 | }
--------------------------------------------------------------------------------
/src/styles/plugins/slider.less:
--------------------------------------------------------------------------------
1 | //
2 | // Slider
3 | // @desc: 滑动图片,slider.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}slider{
7 | display:block;
8 | li{}
9 | img{ display:block; margin:0 auto;}
10 | }
11 |
12 | .bx-wrapper{
13 | position:relative;
14 |
15 | .bx-controls{}
16 | .bx-pager{ position:absolute; right:10px; bottom:10px; text-align:right;}
17 | .bx-pager-item{ display:inline-block; padding:0 3px;}
18 | .bx-pager-link{
19 | display:inline-block; width:8px; height:8px; background:rgba(0,0,0,.6); border-radius:999rem; font-size:0; overflow:hidden;
20 | &.active{ background:#38c483;}
21 | }
22 |
23 | .bx-controls-direction{ display:none; }
24 | .bx-prev{}
25 | .bx-next{}
26 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "squirrel",
3 | "version": "0.10.0",
4 | "description": "",
5 | "main": "gulpfile.js",
6 | "devDependencies": {
7 | "del": "^0.1.3",
8 | "gulp-autoprefixer": "^1.0.1",
9 | "gulp-cache": "^0.2.4",
10 | "gulp-concat": "^2.4.1",
11 | "gulp-footer": "^1.0.5",
12 | "gulp-header": "^1.2.2",
13 | "gulp-if": "^1.2.5",
14 | "gulp-imagemin": "^1.2.1",
15 | "gulp-jshint": "^1.8.5",
16 | "gulp-less": "^1.3.6",
17 | "gulp-livereload": "^2.1.1",
18 | "gulp-minify-css": "^0.3.11",
19 | "gulp-notify": "^2.0.1",
20 | "gulp-plumber": "^0.6.6",
21 | "gulp-rename": "^1.2.0",
22 | "gulp-replace": "^0.5.0",
23 | "gulp-sourcemaps": "^1.2.4",
24 | "gulp-uglify": "^1.0.1"
25 | },
26 | "author": "iinterest.bell@gmail.com",
27 | "license": "ISC"
28 | }
29 |
--------------------------------------------------------------------------------
/src/styles/gallery.less:
--------------------------------------------------------------------------------
1 | //
2 | // Gallery
3 | // @desc: 画廊
4 | // ---------------------------------------------------------------------------
5 |
6 | .@{ns}gallery{
7 | width:100%; overflow:hidden;
8 | ul{ margin:-@gallery-space;}
9 | a{ display:block; position:relative;}
10 | img{ display:block; width:100%;}
11 |
12 | .gallery-item{ position:relative; padding:@gallery-space;}
13 | .title{ font-size:@gallery-title-font-size;}
14 | .desc{ font-size:@gallery-title-font-desc;}
15 |
16 |
17 | // overlay
18 | // @desc: 浮层标题
19 | // --------------------------------------------
20 | &.overlay{
21 | .title{ position:absolute; right:0; bottom:0; left:0; margin:0; padding:@gallery-space; background:@gallery-overlay-title-bg; color:@gallery-overlay-title-color;}
22 | .desc{ display:none;}
23 | }
24 | }
--------------------------------------------------------------------------------
/src/styles/column.less:
--------------------------------------------------------------------------------
1 | //
2 | // Column Grid
3 | // 分栏
4 | // ---------------------------------------------------------------------------
5 |
6 | .make-col-grid(@point: sm, @counter: @grid-columns, @i: 1) when (@i =< @counter) {
7 | .@{ns}col-@{point}-@{i} > li,
8 | .@{ns}col-@{point}-@{i} > a{
9 | width: (100% / @i);
10 | &:nth-of-type(n) {
11 | clear: none;
12 | }
13 | &:nth-of-type(@{i}n+1) {
14 | clear: both;
15 | }
16 | }
17 | .make-col-grid(@point, @counter, (@i + 1));
18 | }
19 |
20 |
21 | [class*="@{ns}col-"] {
22 | display:block; padding:0; margin:0; .clearfix();
23 | &> li,
24 | &> a{ display:block; float:left; height:auto;}
25 | }
26 |
27 |
28 | @media @mobile-screen {
29 | .make-col-grid(mb);
30 | }
31 |
32 | @media @table-screen {
33 | .make-col-grid(tb);
34 | }
--------------------------------------------------------------------------------
/src/styles/plugins/modal.less:
--------------------------------------------------------------------------------
1 | //
2 | // Modal
3 | // @desc: 弹出窗口,modal.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}modal{
7 | background:@modal-bg; border-radius:@modal-radius;
8 |
9 | //.content{}
10 |
11 | // 按钮
12 | .close-btn{ position:absolute; top:0; right:0; width:30px; height:30px; background:#d7d7d7; border-radius:3px; text-align:center; line-height:30px; cursor:pointer; z-index:3;}
13 | .ok-btn,
14 | .cancel-btn{ position:absolute; width:50%; padding:@modal-btn-padding; background:#efefef; border-top:1px solid @modal-border-color; text-align:center; cursor:pointer;}
15 | .ok-btn{ left:0; bottom:0; border-radius:0 0 0 @modal-radius;}
16 | .cancel-btn{ right:0; bottom:0; border-left:1px solid @modal-border-color; border-radius:0 0 @modal-radius 0;}
17 | }
--------------------------------------------------------------------------------
/src/styles/plugins/loadmore.less:
--------------------------------------------------------------------------------
1 | //
2 | // Loadmore
3 | // @desc: 下拉菜单,loadmore.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}loadmore-state{
7 | position:relative; padding:@loadmore-padding; line-height:@loadmore-lin-height; color:@default-font-color; text-align:center; cursor:pointer; overflow:hidden;
8 | .state-icon{
9 | &::before{ font-family:@icon-font-family;}
10 | }
11 | .state-txt{ padding:0 8px;}
12 | &.click{
13 | & > .state-icon::before{ content:'\f103';}
14 | }
15 | &.loading{
16 | & > .state-icon{ position:relative; top:-1px; animation:icon-spin 2s infinite linear;}
17 | & > .state-icon::before{ content:'\f110';}
18 | }
19 | //&.error{}
20 | &.no-more{
21 | & > .state-icon::before{ content:'';}
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/styles/squirrel.less:
--------------------------------------------------------------------------------
1 | //
2 | // Squirrel LESS
3 | // ---------------------------------------------------------------------------
4 |
5 | // Variables and mixins
6 | @import "variables";
7 | @import "mixins";
8 |
9 |
10 | // Normalize and Core
11 | @import "normalize";
12 | @import "base";
13 | @import "utilities";
14 |
15 |
16 | // CSS
17 | @import "button";
18 | @import "column";
19 | @import "form";
20 | @import "grid";
21 |
22 |
23 | // Components
24 | @import "animate";
25 | @import "badge";
26 | @import "bar";
27 | @import "gallery";
28 | @import "list";
29 | @import "font-awesome";
30 |
31 |
32 | // Plugins
33 | @import "plugins/dropdown";
34 | @import "plugins/loadmore";
35 | @import "plugins/panel";
36 | @import "plugins/modal";
37 | @import "plugins/tabs";
38 |
39 | @import "plugins/slider";
40 | @import "plugins/screenshot";
41 |
42 |
43 | // Theme
44 | //@import "themes/default-theme";
--------------------------------------------------------------------------------
/src/styles/base.less:
--------------------------------------------------------------------------------
1 | //
2 | // Base
3 | // @desc: 定制化样式
4 | // ---------------------------------------------------------------------------
5 |
6 | *,
7 | *:before,
8 | *:after{ box-sizing:border-box; margin:0; padding:0;}
9 |
10 | html{ font-size:62.5%;}
11 |
12 | body{
13 | //-webkit-font-smoothing:antialiased;
14 | //** 暂时屏蔽
15 | //-webkit-tap-highlight-color:transparent;
16 | -webkit-touch-callout:none;
17 |
18 | font-family:@font-family;
19 | font-size:@default-font-size;
20 | color:@default-font-color;
21 | word-wrap:break-word;
22 | break-word:break-all;
23 | }
24 |
25 | h1,
26 | h2,
27 | h3,
28 | h4,
29 | h5{ font-weight:400;}
30 |
31 | ul,
32 | ol,
33 | menu{ list-style:none;}
34 |
35 | input,
36 | textarea,
37 | select{ -webkit-font-smoothing:antialiased;}
38 |
39 | a{ color:@default-font-color; text-decoration:none;}
40 | b,
41 | i,
42 | em{ font-weight:normal; font-style:normal;}
43 |
--------------------------------------------------------------------------------
/src/styles/plugins/dropdown.less:
--------------------------------------------------------------------------------
1 | //
2 | // Dropdown
3 | // @desc: 下拉菜单,dropdown.js 配套样式
4 | // @author: squirrel
5 | // ---------------------------------------------------------------------------
6 | .@{ns}dropdown{
7 | position:relative; display:inline-block; vertical-align:middle;
8 |
9 | .dropdown-content{
10 | display:none; position:absolute; top:100%; left:0; min-width:@dropdown-min-width; margin-top:-1px; background:@dropdown-content-bg; box-shadow:@shardow-normal; border-radius:@dropdown-content-radius; border:1px solid @dropdown-border-color; z-index:@dropdown-index;
11 | & > li{
12 | padding:@dropdown-content-padding; border-bottom:1px solid @dropdown-border-color;
13 | &:hover{ background:@dropdown-hover;}
14 | &:last-of-type{ border-bottom:none;}
15 | }
16 | a{ display:block; white-space:nowrap; line-height:@default-line-height;}
17 | &.align-right{ left:auto; right:0;}
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/example/js/example.js:
--------------------------------------------------------------------------------
1 | (function init() {
2 | 'use strict';
3 | $('.J_menu').dropdown({
4 | ANIMATE: '.fadeIn quick'
5 | });
6 |
7 | $('.J_fixedGoTop').sticky({
8 | ARRY_FIXED_POSITION: ['auto', 10, 20, 'auto'],
9 | NUM_TRIGGER_POSITION: 300,
10 | fixedIn: function () {
11 | this.$element.show();
12 | },
13 | fixedOut: function () {
14 | this.$element.hide();
15 | }
16 | });
17 |
18 | $('.J_goBack').on('click', function (e) {
19 | e.preventDefault();
20 | history.go(-1);
21 | });
22 | }());
23 |
24 | var $animateDemo = $('#animate-demo');
25 | $('.animate-effect').on('click', '.sq-btn', function (e) {
26 | 'use strict';
27 | e.preventDefault();
28 | var $btn = $(this);
29 | var animateEffect = $btn.attr('data-effect');
30 | $btn.addClass('mt-green').siblings().removeClass('mt-green');
31 | $animateDemo.removeClass().addClass('animated ' + animateEffect);
32 | });
33 |
34 |
35 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2010 - 2030 iinterest, [http://hisquirrel.com]()
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
--------------------------------------------------------------------------------
/src/styles/plugins/screenshot.less:
--------------------------------------------------------------------------------
1 | //** sq-screenshot
2 | //** Screenshot
3 |
4 | .@{ns}screenshot{
5 | .screenshot-horizontal{
6 | margin-top:20px; max-height:400px; overflow:hidden;
7 | // 默认为全景模式
8 | ul{ list-style:none; margin:0; padding:0; overflow:hidden;}
9 | li{ float:left; width:320px; margin:0 10px 0 0; padding:0; background:#e5e5e5; text-align:center; cursor:pointer;
10 | img{ display:block; width:100%;}
11 | &.active{ color:#fff; background:#e5e5e5;}
12 | }
13 | // 肖像模式
14 | .portrait-view{
15 | li{ width:200px;}
16 | }
17 | .landscape-view{}
18 | }
19 |
20 | /* Scrollbar */
21 | .screenshot-scrollbar{
22 | margin: 10px 0 0 0;
23 | height: 5px;
24 | background: #e5e5e5;
25 | line-height: 0;
26 |
27 | .handle {
28 | width: 100px;
29 | height: 100%;
30 | background: #38c483;
31 | cursor: pointer;
32 |
33 | .mousearea {
34 | position: absolute;
35 | top: -9px;
36 | left: 0;
37 | width: 100%;
38 | height: 20px;
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/styles/mixins.less:
--------------------------------------------------------------------------------
1 | //
2 | // Mixin
3 | // @desc: LESS 混合模块
4 | // ---------------------------------------------------------------------------
5 |
6 | // 清除浮动
7 | .clearfix() {
8 | &:before,
9 | &:after {
10 | content: " ";
11 | display: table;
12 | }
13 | &:after {
14 | clear: both;
15 | }
16 | }
17 |
18 | // 折行字体隐藏
19 | .text-hide() {
20 | display:block;
21 | max-height:1.4em;
22 | line-height:1.4em;
23 | overflow:hidden;
24 | }
25 |
26 | // 折行字体显示省略号
27 | .text-hide-ellipsis() {
28 | display:block;
29 | white-space:nowrap;
30 | text-overflow:ellipsis;
31 | overflow:hidden;
32 | }
33 |
34 | // 元素平铺
35 | .overlay (@top: 0, @right: 0, @bottom: 0, @left: 0){
36 | position:absolute;
37 | top: @top;
38 | right: @right;
39 | bottom: @bottom;
40 | left: @left;
41 | }
42 |
43 | // 三角箭头
44 | .sq-arr-up (@size, @color){
45 | width:0; height:0; border:@size solid transparent; border-bottom-color:@color;
46 | }
47 | .sq-arr-right (@size, @color){
48 | width:0; height:0; border:@size solid transparent; border-right-color:@color;
49 | }
50 | .sq-arr-down (@size, @color){
51 | width:0; height:0; border:@size solid transparent; border-down-color:@color;
52 | }
53 | .sq-arr-left (@size, @color){
54 | width:0; height:0; border:@size solid transparent; border-left-color:@color;
55 | }
--------------------------------------------------------------------------------
/example/example.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Example
11 |
12 |
13 |
14 |
15 |
16 | Example
17 |
25 |
26 |
29 |
30 | Top
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/example/css/example.css:
--------------------------------------------------------------------------------
1 | .mb--1{ margin-bottom:-1px !important;}
2 |
3 | /*common*/
4 | .example{ padding:0 10px 40px;}
5 | .example-title{ position:relative; margin:2em 0 .5em; padding:.5em; background:rgba(255, 255, 255, .8); border-bottom:1px solid #38c483; font-size:18px; color:#38c483; z-index:1; clear:both;}
6 | .example-title:first-of-type{ margin-top:.5em;}
7 | .example-row{ padding:5px;}
8 | .example hr{ margin:20px 0; border:none; border-bottom:1px dashed #eee;}
9 | .footer{ height:20px; margin-top:50px; background:#eee; text-align:center; font-size:12px; line-height:20px;}
10 | .example-space{ height:700px;}
11 | .go-top-btn{ display:none; padding:10px; border-radius:999em; background:#38c483; color:#fff;}
12 | /*animate*/
13 | .example .animate-box{ padding:10px; height:150px;}
14 | .example #animate-demo{ position:fixed; top:70px; left:50%; width:240px; height:130px; margin-left:-120px; background:rgba(0, 0, 0, .6); line-height:130px; text-align:center; border-radius:4px; color:#fff; z-index:10;}
15 | .example .animate-effect h3{ font-size:16px;}
16 | .example .animate-effect .sq-btn{ margin-bottom:5px;}
17 | /*bar*/
18 | .example .sq-bar{ background:#eee; color:#333;}
19 | .example .sq-bar a{ color:#333;}
20 | .example .sq-bar .active{ color:#fff;}
21 | /*glyphicon*/
22 | .example .fontawesome-icon-list > div{ margin:10px 0; font-size:12px;}
23 | /*grid*/
24 | .example-grid > .row > div{ background:#eee; border:1px solid #38c483;}
25 |
26 |
27 | .sq-slider{ height:150px; background:#eee; overflow:hidden;}
28 | .sq-slider li{ height:150px;}
29 | .bx-wrapper{}
30 | .bx-wrapper .bx-pager{}
31 | .bx-wrapper .bx-pager-item{}
32 | .bx-wrapper .bx-pager-link{}
33 | .bx-wrapper .bx-pager-link.active{ background:#38c483;}
34 | .bx-wrapper .bx-controls-direction{}
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Squirrle 3
2 |
3 | ## 一、简介
4 |
5 | Squirrle 是一款轻量级的移动 Web 前端开发框架,提供简单、快速的 Web 开发体验。
6 |
7 | ## 二、特性
8 |
9 | * 专为移动终端设计,支持 Android、iOS、WP 操作系统。
10 | * 适应各种屏幕尺寸、分辨率的移动终端,针对触摸操作体验进行了优化。
11 | * 遵循 MIT 协议,无论公司还是个人,都可以免费、自由使用。
12 | * 提供丰富的样式及交互组件,能帮助您轻松构建 Web 应用。
13 | * 采用 LESS 技术、CMD 规范、jQuery 插件开发模式,扩展方便。
14 | * 提供定制工具,可以根据实际需要灵活的定制框架。
15 |
16 | ## 三、使用指南
17 |
18 | ### 1、安装
19 |
20 | Squirrel 支持两种安装方式:
21 |
22 | 1. 直接下载 Squirrle 3 [代码包](https://github.com/iinterest/Squirrel-3/releases)
23 | 2. 或者使用 npm 安装:
24 |
25 | npm install squirrel-pt
26 |
27 | 安装完成或压缩下载的安装包之后,可看到以下目录结构:
28 |
29 | squirrel/
30 | |
31 | ├── css/
32 | │ ├── squirrel.min.css
33 | │ └── app.css
34 | ├── fonts/
35 | │ ├── fontawesome-webfont.ttf
36 | │ └── fontawesome-webfont.woff
37 | ├── images/
38 | ├── js/
39 | │ ├── jquery.min.js
40 | │ ├── jquery.min.map
41 | │ ├── squirrel.min.js
42 | │ └── app.js
43 | ├── maps/
44 | │ ├── squirrel.min.css.map
45 | │ └── squirrel.min.js.map
46 | └── index.html
47 |
48 | ### 2、使用
49 |
50 | Squirrel 安装包内包含编译并压缩好的 squirrel.min.css、squirrel.min.js 文件,以及最新的 jQuery 库和 Awesome 的图标字体,同时还提供 Javascript 和 CSS 的源码映射表(.map 文件);除此之外还提供简单的模板样式文件(Boilerplate)您可以:
51 |
52 | * 以index.html为样板编写 HTML;
53 | * 在app.css中编写 CSS;
54 | * 在app.js中编写 JavaScript;
55 | * 将图片资源放在images目录下。
56 |
57 | ## 三、贡献力量
58 |
59 | Squirrel 3 是遵循 MIT 协议的开源项目,代码寄托于 [GitHub](https://github.com/iinterest/Squirrel-3),如果感兴趣请加入我们贡献您的力量,您可以:
60 |
61 | 1. 希望您能反馈任何有关体验方面的意见和建议至 [Github issues](https://github.com/iinterest/Squirrel-3/issues)「体验」,您可以提出问题或改进建议,甚至提出新的功能需求。
62 | 2. 如果使用中发现 BUG,请提交至 [Github issues](https://github.com/iinterest/Squirrel-3/issues)「BUG」,同时也请报告您的使用版本、环境,我们会尽快确认、修复。
63 | 3. 欢迎您为 Squirrel 贡献高实用性的功能插件,以方便其他使用者。开始之前请先阅读插件开发流程。
64 | 4. 如果您制作了一款基于 Squirrel 的主题并且符合主题的开发规范,我们非常欢迎您将它提交至主题库。
65 | 5. DPL 组件与业务结合紧密,具备高质量且完整的交互体验,如果您为您的项目开发了类似的组件,欢迎提交至设计模式库。
66 |
67 |
--------------------------------------------------------------------------------
/example/column.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Column
11 |
12 |
13 |
14 |
15 |
16 | Column
17 |
25 |
26 |
27 | Column
28 |
29 |
30 | - 1
31 | - 2
32 | - 3
33 | - 4
34 | - 5
35 |
36 |
37 |
38 |
39 | Top
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/example/panel.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Panel
11 |
12 |
13 |
14 |
15 |
16 | Panel
17 |
25 |
26 |
30 |
31 | Top
32 |
33 |
34 |
35 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/example/badge.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Badge
11 |
12 |
13 |
14 |
15 |
16 | Badge
17 |
25 |
26 |
27 | Badge
28 | Hot
29 | 29
30 | 最新
31 | 首发
32 | badge
33 | badge
34 |
35 |
36 |
37 | Top
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/example/slider.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Slider
11 |
12 |
13 |
14 |
15 |
16 | Slider
17 |
25 |
26 |
27 | Slider
28 |
33 |
34 |
35 |
36 | Top
37 |
38 |
39 |
40 |
41 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/example/core-function.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Example CoreFun
11 |
12 |
13 |
14 |
15 |
16 | Core Function
17 |
22 |
23 |
35 |
36 |
37 |
38 |
39 |
48 |
49 |
50 |
--------------------------------------------------------------------------------
/example/modal.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Modal
11 |
12 |
13 |
14 |
15 |
16 | Modal
17 |
25 |
26 |
27 | Modal
28 |
29 |
30 |
31 | Top
32 |
33 |
39 |
40 |
41 |
42 |
43 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/example/grid.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Grid
11 |
12 |
13 |
14 |
26 |
27 | Grid
28 |
29 |
30 |
col-mb-4
31 |
col-mb-4
32 |
col-mb-4
33 |
34 |
35 |
col-mb-6
36 |
col-mb-6
37 |
38 |
39 |
col-mb-8
40 |
col-mb-2
41 |
col-mb-2
42 |
43 |
44 |
45 |
46 | Top
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/example/tab.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Tab
11 |
12 |
13 |
14 |
26 |
27 | Tab
28 |
29 |
30 | - tab 1
31 | - tab 2
32 | - tab 3
33 |
34 |
35 |
Proin elit arcu, ...
36 |
37 |
38 |
Morbi tincidunt, ...
39 |
40 |
41 |
Duis cursus ...
42 |
43 |
44 |
45 |
46 | Top
47 |
48 |
49 |
50 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/src/scripts/core/util.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.util
3 | * 常用函数
4 | * @version 1.0.0
5 | */
6 | /*global SQ*/
7 | SQ.util = {
8 | /**
9 | * 随机数输出
10 | * @method
11 | * @name SQ.util.generate
12 | * @example
13 | * Sq.util.generate.uniqueId();
14 | * Sq.util.generate.randomInt(0, 9);
15 | * Sq.util.generate.randomArr([1,2,3]);
16 | */
17 | generate: {
18 | // 生成唯一标识符
19 | uniqueId: function () {
20 |
21 | },
22 | randomInt: function (min, max) {
23 | 'use strict';
24 | if (typeof min === 'number' && typeof max === 'number' && min < max) {
25 | return parseInt(Math.random() * (max - min + 1) + min, 10);
26 | }
27 | return false;
28 | },
29 | randomArr: function (arr) {
30 | 'use strict';
31 | return arr.sort(function () {
32 | return Math.random() - 0.5;
33 | });
34 | }
35 | },
36 | /**
37 | * 字符串操作
38 | * @method
39 | * @name SQ.util.string
40 | * @example
41 | * SQ.util.string.trim(' test string ');
42 | * //return test string
43 | */
44 | string: {
45 | // 过滤字符串首尾的空格
46 | trim: function(srt) {
47 | 'use strict';
48 | return srt.replace(/^\s+|\s+$/g, '');
49 | }
50 | },
51 | /**
52 | * 格式化时间
53 | * @method
54 | * @name SQ.util.dateToString
55 | * @example
56 | * SQ.util.dateToString(new Date())
57 | * //return 2013-10-17 17:31:58
58 | */
59 | dateToString: function(time) {
60 | 'use strict';
61 | var year = time.getFullYear();
62 | var month = time.getMonth() + 1;
63 | var date = time.getDate();
64 | var hours = time.getHours();
65 | var min = time.getMinutes();
66 | var sec = time.getSeconds();
67 |
68 | month = month < 10 ? ('0' + month) : month;
69 | date = date < 10 ? ('0' + date) : date;
70 | hours = hours < 10 ? ('0' + hours) : hours;
71 | min = min < 10 ? ('0' + min) : min;
72 | sec = sec < 10 ? ('0' + sec) : sec;
73 |
74 | var dateString = year + '-' + month + '-' + date + ' ' + hours + ':' + min + ':' + sec;
75 | return dateString;
76 | },
77 | goTop: function (e) {
78 | 'use strict';
79 | e.preventDefault();
80 | window.scrollTo(0, 0);
81 | },
82 | goBack: function (e) {
83 | 'use strict';
84 | e.preventDefault();
85 | history.back();
86 | }
87 | };
--------------------------------------------------------------------------------
/src/styles/button.less:
--------------------------------------------------------------------------------
1 | //
2 | // Button
3 | // @desc: 通用按钮,包括单体按钮、组合按钮。
4 | // ---------------------------------------------------------------------------
5 |
6 | .@{ns}btn{
7 | display:inline-block; position:relative; border-radius:@btn-border-radius; border:1px solid transparent; text-align:center; white-space:nowrap; line-height:1; cursor:pointer; outline:none; user-select:none;
8 |
9 |
10 | // Size
11 | // @desc: 按钮尺寸
12 | // --------------------------------------------
13 | padding:@btn-padding-normal;
14 | &.large{ padding:@btn-padding-large; font-size:@btn-font-size-large;}
15 | &.small{ padding:@btn-padding-small; font-size:@btn-font-size-small;}
16 | &.mini{ padding:@btn-padding-mini; font-size:@btn-font-size-mini;}
17 | &.block{ display:block; width:100%; clear:both;}
18 |
19 |
20 | // disabled
21 | // @desc: 禁用样式
22 | // --------------------------------------------
23 | &.disabled,
24 | &[disabled]{
25 | pointer-events:none; cursor:not-allowed;
26 | opacity:@btn-disabled-opacity;
27 | }
28 |
29 | // color
30 | //
31 | &.mt-white{
32 | background:#f7f7f7; color:#333; border-color:#ddd;
33 | & > a{ color:#333;}
34 | }
35 | &.mt-grey{
36 | background:#e1e1e1; border-color:#e1e1e1; color:#333;
37 | & > a{ color:#333;}
38 | }
39 | &.mt-black{
40 | background:#444; border-color:#444; color:#fff;
41 | & > a{ color:#fff;}
42 | }
43 | &.mt-blue{
44 | background:#4285f4; border-color:#4285f4; color:#fff;
45 | & > a{ color:#fff;}
46 | &:hover{ background:#3367d6;}
47 | }
48 | &.mt-green{ background:#6eb82c; border-color:#6eb82c; color:#fff;
49 | & > a{ color:#fff;}
50 | }
51 | &.mt-orange{ background:#faa800; border-color:#faa800; color:#fff;
52 | & > a{ color:#fff;}
53 | }
54 | &.mt-red{ background:#e34541; border-color:#e34541; color:#fff;
55 | & > a{ color:#fff;}
56 | }
57 | }
58 |
59 |
60 | // Button Group
61 | // @desc: 组合按钮
62 | // --------------------------------------------
63 | .sq-btn-group{
64 | display:flex; flex-direction:row;
65 | .active{
66 | z-index:1; // 保证激活项目在最上层显示
67 | }
68 | & > .sq-btn{
69 | display:block; flex:1; margin-left:-1px; border-radius:0; .text-hide-ellipsis();
70 | &:first-child{ margin-left:0; border-radius:@btn-border-radius 0 0 @btn-border-radius;}
71 | &:last-child{ border-radius:0 @btn-border-radius @btn-border-radius 0;}
72 | }
73 | // 垂直布局
74 | &.vertical{
75 | flex-direction:column;
76 | & > .sq-btn{
77 | margin:-1px 0 0 0;
78 | &:first-child{ margin-top:0; border-radius:@btn-border-radius @btn-border-radius 0 0;}
79 | &:last-child{ border-radius:0 0 @btn-border-radius @btn-border-radius;}
80 | }
81 | }
82 | }
--------------------------------------------------------------------------------
/src/scripts/core/ua.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.ua
3 | * 获取设备 ua 信息,判断系统版本、浏览器名称及版本
4 | * @version 1.0.0
5 | */
6 | /*global SQ*/
7 | SQ.ua = (function () {
8 | 'use strict';
9 | var info = {};
10 | var ua = navigator.userAgent;
11 | var m;
12 |
13 | info.os = {};
14 | info.browser = {};
15 |
16 | /**
17 | * operating system. android, ios, linux, windows
18 | * @type string
19 | */
20 | if ((/Android/i).test(ua)) {
21 | info.os.name = 'android';
22 | info.os.version = ua.match(/(Android)\s([\d.]+)/)[2];
23 | } else if ((/Adr/i).test(ua)) {
24 | // UC 浏览器极速模式下,Android 系统的 UA 为 'Adr'
25 | info.os.name = 'android';
26 | info.os.version = ua.match(/(Adr)\s([\d.]+)/)[2];
27 | } else if ((/iPod/i).test(ua)) {
28 | info.os.name = 'ios';
29 | info.os.version = ua.match(/OS\s([\d_]+)/)[1].replace(/_/g, '.');
30 | info.device = 'ipod';
31 | } else if ((/iPhone/i).test(ua)) {
32 | info.os.name = 'ios';
33 | info.os.version = ua.match(/(iPhone\sOS)\s([\d_]+)/)[2].replace(/_/g, '.');
34 | info.device = 'iphone';
35 | } else if ((/iPad/i).test(ua)) {
36 | info.os.name = 'ios';
37 | info.os.version = ua.match(/OS\s([\d_]+)/)[1].replace(/_/g, '.');
38 | info.device = 'ipad';
39 | }
40 |
41 | // 浏览器判断
42 | m = ua.match(/AppleWebKit\/([\d.]*)/);
43 | if (m && m[1]) {
44 | info.browser.core = 'webkit';
45 | info.browser.version = m[1];
46 |
47 | if ((/Chrome/i).test(ua)) {
48 | info.browser.shell = 'chrome';
49 | } else if ((/Safari/i).test(ua)) {
50 | info.browser.shell = 'safari';
51 | } else if ((/Opera/i).test(ua)) {
52 | info.browser.shell = 'opera';
53 | }
54 | }
55 |
56 | if ((/UCBrowser/i).test(ua)) {
57 | // UCWeb 9.0 UA 信息中包含 UCBrowser 字段
58 | m = ua.match(/(UCBrowser)\/([\d.]+)/);
59 | info.browser.shell = 'ucweb';
60 | info.browser.version = m[2];
61 | } else if ((/UCWEB/i).test(ua)) {
62 | // UCWeb 7.9 UA 信息中包含 UCWEB 字段
63 | m = ua.match(/(UCWEB)([\d.]+)/);
64 | info.browser.shell = 'ucweb';
65 | info.browser.version = m[2];
66 | } else if ((/UC/i).test(ua)) {
67 | // UCWeb 8.x UA 信息中包含 UC 字段
68 | // 确认 8.6、8.7
69 | info.browser.shell = 'ucweb';
70 | info.browser.version = '8.x';
71 | }
72 |
73 | if (info.browser.shell === 'ucweb') {
74 | // UC 浏览器急速模式
75 | // 目前只有 Android 平台国内版 UCWeb 9.0 可以判断是否为急速模式,UA 中包含 UCWEB/2.0 字段即为急速模式。
76 | if ((/UCWEB\/2\.0/i).test(ua)) {
77 | info.browser.module = 'swift';
78 | }
79 | }
80 |
81 | if (info.browser.version) {
82 | info.browser.version = parseFloat(info.browser.version, 10);
83 | }
84 |
85 | return info;
86 | }());
--------------------------------------------------------------------------------
/example/dropdown.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Dropdown
11 |
12 |
13 |
14 |
15 |
16 | Dropdown
17 |
25 |
26 |
27 | Example
28 |
29 |
37 |
38 |
46 |
47 |
48 |
49 |
57 |
58 |
59 | Top
60 |
61 |
62 |
63 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/src/styles/grid.less:
--------------------------------------------------------------------------------
1 | //
2 | // Grid
3 | // @desc: 栅格系统
4 | // ---------------------------------------------------------------------------
5 |
6 | .col-tb-visible{ display:none;}
7 |
8 | //.row{.clearfix();}
9 |
10 |
11 | // Size
12 | // @desc: 生成栏,参照 bootstrap Grid
13 | // --------------------------------------------
14 | .make-grid-columns();
15 |
16 | .make-grid-columns() {
17 | .col(@index) when (@index = 1) { // initial
18 | @item: ~".col-mb-@{index}, .col-tb-@{index}";
19 | .col((@index + 1), @item);
20 | }
21 | .col(@index, @list) when (@index =< @grid-columns) { // general; "=<" isn't a typo
22 | @item: ~".col-mb-@{index}, .col-tb-@{index}";
23 | .col((@index + 1), ~"@{list}, @{item}");
24 | }
25 | .col(@index, @list) when (@index > @grid-columns) { // terminal
26 | @{list} {
27 | position: relative;
28 | // Prevent columns from collapsing when empty
29 | min-height: 1px;
30 | // Inner gutter via padding
31 | padding-left: (@grid-gutter-width / 2);
32 | padding-right: (@grid-gutter-width / 2);
33 | }
34 | }
35 | .col(1); // kickstart it
36 | }
37 |
38 | .float-grid-columns(@class) {
39 | .col(@index) when (@index = 1) { // initial
40 | @item: ~".col-@{class}-@{index}";
41 | .col((@index + 1), @item);
42 | }
43 | .col(@index, @list) when (@index =< @grid-columns) { // general
44 | @item: ~".col-@{class}-@{index}";
45 | .col((@index + 1), ~"@{list}, @{item}");
46 | }
47 | .col(@index, @list) when (@index > @grid-columns) { // terminal
48 | @{list} {
49 | float: left;
50 | }
51 | }
52 | .col(1); // kickstart it
53 | }
54 |
55 | .calc-grid-column(@index, @class, @type) when (@type = width) and (@index > 0) {
56 | .col-@{class}-@{index} {
57 | width: percentage((@index / @grid-columns));
58 | }
59 | }
60 | .calc-grid-column(@index, @class, @type) when (@type = push) {
61 | .col-@{class}-push-@{index} {
62 | left: percentage((@index / @grid-columns));
63 | }
64 | }
65 | .calc-grid-column(@index, @class, @type) when (@type = pull) {
66 | .col-@{class}-pull-@{index} {
67 | right: percentage((@index / @grid-columns));
68 | }
69 | }
70 | .calc-grid-column(@index, @class, @type) when (@type = offset) {
71 | .col-@{class}-offset-@{index} {
72 | margin-left: percentage((@index / @grid-columns));
73 | }
74 | }
75 |
76 | // Basic looping in LESS
77 | .loop-grid-columns(@index, @class, @type) when (@index >= 0) {
78 | .calc-grid-column(@index, @class, @type);
79 | // next iteration
80 | .loop-grid-columns((@index - 1), @class, @type);
81 | }
82 |
83 | // Create grid for specific class
84 | .make-grid(@class) {
85 | .float-grid-columns(@class);
86 | .loop-grid-columns(@grid-columns, @class, width);
87 | .loop-grid-columns(@grid-columns, @class, pull);
88 | .loop-grid-columns(@grid-columns, @class, push);
89 | .loop-grid-columns(@grid-columns, @class, offset);
90 | }
91 |
92 |
93 | @media @mobile-screen {
94 | .make-grid(mb);
95 | }
96 |
97 | @media @table-screen {
98 | .make-grid(tb);
99 | .col-tb-visible{ display:block;}
100 | }
--------------------------------------------------------------------------------
/src/styles/themes/default-theme.less:
--------------------------------------------------------------------------------
1 | //
2 | // Material UI
3 | // ---------------------------------------------------------------------------
4 |
5 | @ns: ~"sq-";
6 | @space-normal: 16px;
7 |
8 |
9 | @white: #fff;
10 | @font-grey: #757575;
11 | @base-blue: #3f51b5;
12 | @border-color-grey: #e0e0e0;
13 |
14 |
15 | @shadow-normal: 0 2px 5px rgba(0,0,0,0.26);
16 | @shadow-depth-2: 0 1px 6px rgba(0, 0, 0, 0.36);
17 |
18 | hr{ display:block; height:1px; background:@border-color-grey; margin:8px 0; border:none; clear:both;}
19 |
20 | .@{ns}bar{
21 | height:56px; padding:0; font-size:20px; background:@base-blue; color:@white; line-height:56px;
22 | // 超过 768px: height:64px; font-size:24px;
23 |
24 | box-shadow:@shadow-normal;
25 |
26 | &.nav{
27 | & > .title{ text-align:left; font-size:20px; margin:0; padding:0 16px;}
28 | & > .extend{}
29 | .sq-btn{
30 | width:48px; height:48px; padding:0; margin:4px; color:@white; line-height:48px;
31 | }
32 | }
33 | }
34 |
35 | .@{ns}list{
36 | .group-hd{ font-size:1.6rem; color:@font-grey;}
37 | .list-item{
38 | padding:(@space-normal - 1) @space-normal;
39 | &:after{ content:''; display:block; position:absolute; left:72px; right:0; bottom:-1px; height:1px; border-bottom:1px solid transparent;}
40 | &.divider{ border-color:@border-color-grey;} //等屏宽分隔线 Full-bleed dividers
41 | &.divider-inset:after{ border-color:@border-color-grey;}
42 | & > .icon:last-child{ width:auto;}
43 | }
44 | &.divider{
45 | & > .list-item{ border-color:@border-color-grey;}
46 | }
47 | &.divider-inset{
48 | & > .list-item:after{ border-color:@border-color-grey;}
49 | }
50 | &.single-line{
51 | & > .list-item{ padding:7px @space-normal;}
52 | }
53 |
54 | // 保证文字以 75px 对齐
55 | .sq-icon{ width:56px;}
56 | .icon{
57 | width:56px; height:auto;
58 | img{ width:40px; border-radius:999rem;}
59 | }
60 |
61 | &.text{
62 | .list-item{
63 | & > a{ padding:(@space-normal - 2) @space-normal (@space-normal - 3);}
64 | }
65 | .sq-icon{
66 | width:56px; margin:0; text-align:left;
67 | &.sq-fr{ width:auto;}
68 | }
69 | }
70 |
71 | &.media{
72 | .title{ line-height:1;}
73 | .desc{ margin-left:0; line-height:1;}
74 | .digest{ color:@font-grey;}
75 | .extend{
76 | .sq-icon{ width:auto;}
77 | }
78 | }
79 | }
80 |
81 | .@{ns}btn{
82 | padding:9px 8px; // height:36px;
83 | &.flat{}
84 | &.raised{ border-radius:2px; box-shadow:@shadow-depth-2;}
85 | &.float-action{}
86 | }
87 |
88 | .@{ns}card{
89 | margin:@space-normal / 2; box-shadow:@shadow-depth-2; border-radius:2px;
90 | & > .hd{ margin:0;}
91 | & > .bd{
92 | padding:@space-normal;
93 | img{ width:100%;}
94 | }
95 | & > .hd + .bd{ padding-top:0;}
96 | & > .action{
97 | padding:@space-normal / 2; border-top:1px solid @border-color-grey;
98 | }
99 |
100 | .card-title{ margin-bottom:@space-normal - 2;}
101 |
102 | .gallery-item{
103 | margin:0; padding:0;
104 | }
105 | }
--------------------------------------------------------------------------------
/src/styles/list.less:
--------------------------------------------------------------------------------
1 | //
2 | // List
3 | // @desc: 列表
4 | // ---------------------------------------------------------------------------
5 |
6 | .@{ns}list{
7 |
8 | .group-hd,
9 | .list-item{
10 | display:block; position:relative; padding:@list-padding; border-top:1px solid transparent; border-bottom:1px solid transparent; font-size:@list-font-size; .clearfix();
11 | line-height:@list-line-height; // 保证中英文文本水平对齐
12 | & + .group-hd,
13 | & + .list-item{ margin-top:-1px;}
14 | &.divider{ border-bottom-color:@list-multi-border-color;} //等屏宽分隔线 Full-bleed dividers
15 | }
16 | &.divider{
17 | & > .list-item{ border-color:@list-multi-border-color;}
18 | }
19 | .icon,
20 | .thumb{
21 | display:block; position:relative;
22 | img{ display:block; width:100%;}
23 | .sq-badge{ position:absolute; top:@list-badge-pos-top; left:@list-badge-pos-left;}
24 | }
25 | //.icon{ width:@list-icon-width; height:@list-icon-height;}
26 | .title{ display:block; margin-bottom:@list-inside-space;}
27 | .overlay{ .overlay(); text-indent:-999em; z-index:@list-overlay-zindex; cursor:pointer;}
28 | .align-middle{ display:flex; align-items:center;}
29 |
30 |
31 | // Text
32 | // @desc: 文字列表,通常只包含文字、徽章或少量控件
33 | // --------------------------------------------
34 | &.text{
35 | .list-item{
36 | padding:0;
37 | & > a{ display:block; padding:@list-padding;}
38 | }
39 | .title{ font-size:@content-font-size;}
40 | .data{ display:inline-block; float:right; width:80px; margin-left:1rem;}
41 | .sq-badge{ margin:-2px 0; margin-left:1rem;} // 修正 sq-badge 过高将 list-item 撑开的问题
42 | //.sq-icon:not(.sq-fr){ display:inline-block; width:48px; margin:-3px 0 -3px -15px; text-align:center;}
43 | .sq-icon{
44 | display:inline-block; margin-right:12px;
45 | &.sq-fr{ margin-right:0;}
46 | }
47 | }
48 |
49 |
50 | // Media
51 | // @desc: 媒体列表,布局复杂、控件丰富的列表
52 | // --------------------------------------------
53 | &.media{
54 | .list-item{ display:flex;}
55 | .desc{
56 | flex:1; margin:0 @list-padding;
57 | .title{ font-size:@title-font-size;}
58 | .digest{ margin-bottom:@list-inside-space; font-size:@content-font-size;}
59 | :last-child{ margin-bottom:0;}
60 | }
61 | // 消除多余边距
62 | :first-child{ margin-left:0;}
63 | :last-child{ margin-right:0;}
64 | // 扩展内容,通常放置按钮、Icon
65 | .extend{
66 | position:relative; z-index:@list-overlay-zindex + 1; // 高于 .overlay
67 | .sq-btn{
68 | float:left;
69 | & + .sq-btn{
70 | margin-left:@list-inside-space;
71 | &.block{ margin-left:0; margin-top:@list-inside-space;}
72 | }
73 | }
74 | }
75 | // 一行多列显示优化
76 | &[class*="@{ns}col-"]{
77 | border-bottom:1px solid @list-multi-border-color; border-left:1px solid @list-multi-border-color;
78 | .list-item{
79 | margin:0 0 0 -1px; border-bottom:none; border-left:1px solid @list-multi-border-color; border-right:1px solid @list-multi-border-color;
80 | }
81 | }
82 | }
83 |
84 |
85 | // Tile
86 | // @desc:平布列表
87 | // --------------------------------------------
88 | &.tile{
89 | .list-item{ float:left; margin:0; padding:@list-tile-padding; text-align:center; border:none;}
90 | .icon{ margin:0 auto @list-inside-space;}
91 | }
92 | }
--------------------------------------------------------------------------------
/example/gallery.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Gallery
11 |
12 |
13 |
14 |
15 |
16 | Gallery
17 |
25 |
26 |
27 | Gallery
28 |
53 |
54 |
55 |
56 |
81 |
82 |
83 | Top
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/example/screenshot.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Screenshot
11 |
42 |
43 |
44 |
45 |
46 |
47 | Screenshot
48 |
56 |
57 |
58 | Screenshot
59 |
64 |
65 |
95 |
96 |
97 |
98 |
99 |
100 |
108 |
109 |
--------------------------------------------------------------------------------
/src/styles/bar.less:
--------------------------------------------------------------------------------
1 | //
2 | // Bar
3 | // @desc: 栏,用于告知用户其所在位置的情境信息,以及帮助用户浏览或执行操作的控件,
4 | // 包含导航栏、工具栏、选项栏等。
5 | // ---------------------------------------------------------------------------
6 |
7 | .@{ns}bar{
8 | height:@bar-normal-height; padding:@bar-toolbar-padding;
9 | .flex{ display:flex;}
10 | .@{ns}btn{ display:block; z-index:1;} // 兼容:Andorid 4.1 无法实现 flexbox 相关属性
11 |
12 |
13 | // Tool bar
14 | // @desc: 工具栏,能实现在不同信息层级结构之间的导航
15 | // --------------------------------------------
16 | &.toolbar{
17 | // flexbox
18 | // http://css-tricks.com/snippets/css/a-guide-to-flexbox/
19 | position:relative; display:flex; align-items:center; justify-content:space-between; z-index:@bar-toolbar-index;
20 |
21 | // 对齐方式
22 | &.align-start,
23 | .align-start{ justify-content:flex-start;}
24 | &.align-end,
25 | .align-end{ justify-content:flex-end;}
26 | }
27 |
28 |
29 | // Navigation bar
30 | // @desc: 导航栏,能实现在不同信息层级结构之间的导航
31 | // @extend: .sq-bar.toolbar
32 | // --------------------------------------------
33 | &.nav:extend(.toolbar all){
34 | // 标题栏
35 | & > .title{ flex:1; height:@bar-normal-height; margin:@bar-nav-extend-margin; font-size:@bar-title-font-size; line-height:@bar-normal-height; text-align:center; overflow:hidden;}
36 | // 扩展区域
37 | & > .extend{
38 | display:flex; align-items:center; justify-content:space-between; flex:1; height:@bar-normal-height; margin:@bar-nav-extend-margin;
39 | .@{ns}btn-group{ width:100%;}
40 | }
41 | }
42 |
43 |
44 | // Pagination bar
45 | // @desc: 分页栏
46 | // @extend: .sq-bar.toolbar
47 | // --------------------------------------------
48 | &.pagination:extend(.toolbar all){
49 | // 翻页
50 | select{ min-height:@bar-normal-height - 10; line-height:1;}
51 | & > .pagination-select{ .overlay(0,80px,0,80px); font-size:@bar-pagination-font-size; text-align:center; line-height:@bar-normal-height;}
52 | .disabled{ pointer-events:none; opacity:.4; cursor:not-allowed;}
53 | }
54 |
55 |
56 | // Tabs
57 | // @desc: 选项栏
58 | // --------------------------------------------
59 | &.nav-tabs{
60 | font-size:@bar-tab-font-size;
61 |
62 | & > a{ display:flex; align-items:center; justify-content:space-between; position:relative; height:@bar-normal-height; text-align:center;}
63 | // 嵌套一层 span 是为了达到垂直居中的效果
64 | span{ display:block; flex:1;}
65 |
66 | // 带图标样式
67 | &.icons{
68 | & > a{ font-size:1.2rem;}
69 | .@{ns}icon{ display:block;}
70 | .@{ns}badge{ left:60%; right:auto;}
71 | }
72 |
73 | .@{ns}badge{
74 | position:absolute; top:-2px; right:10%; padding:4px; min-width:20px; border-radius:@border-radius-circle; line-height:1; z-index:1;
75 | }
76 | }
77 |
78 |
79 | // Search
80 | // @desc: 搜索栏
81 | // @extend: .sq-bar.toolbar
82 | // --------------------------------------------
83 | &.search:extend(.toolbar all){
84 | padding:@bar-search-padding;
85 |
86 | [type="text"],
87 | [type="search"],
88 | [type="submit"],
89 | [type="button"]{ border:none; outline:none;}
90 |
91 | [type="text"],
92 | [type="search"]{
93 | display:block; flex:1; height:@bar-search-height; line-height:@bar-search-height;
94 | & + .@{ns}btn{ margin-left:@bar-search-btn-space;}
95 | }
96 |
97 | form{ display:flex; width:100%;}
98 |
99 | .label{
100 | position:relative; flex:1; background:@bar-search-label-bg;
101 | &.icon-search{
102 | line-height:@bar-search-height;
103 | &::before{ position:relative; top:1px; padding:0 8px; content:@bar-search-icon;}
104 | }
105 | }
106 | .@{ns}btn{ max-height:@bar-search-height + 2; overflow:hidden;}
107 |
108 | /*.clear{
109 | display:none; position:absolute; top:0; right:0; width:30px; height:28px; cursor:pointer;
110 | &::after{ content:"×"; position:absolute; top:5px; left:6px; width:18px; height:18px; background:#ccc; border-radius:@border-radius-circle; text-align:center; line-height:16px; color:#fff;}
111 | }*/
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/styles/form.less:
--------------------------------------------------------------------------------
1 | //
2 | // Form
3 | // @desc: 表单元素
4 | // ---------------------------------------------------------------------------
5 |
6 |
7 | // Normalize
8 | // @desc: 标准化
9 | // --------------------------------------------
10 | label{ display:block; margin:@form-label-margin;}
11 | input[type="checkbox"],
12 | input[type="radio"]{ margin:@form-radio-checkbox-margin; line-height:normal;}
13 | input[type="search"]{ box-sizing:border-box;} // 修复 * 无法匹配 search 的问题
14 | input[type="file"]{ display: block;}
15 |
16 | /*select{ box-shadow:none; outline:none;}
17 | select[multiple],
18 | select[size] { height: auto;}*/
19 |
20 |
21 | // Custom
22 | // @desc: 定制控件
23 | // --------------------------------------------
24 | .@{ns}form{
25 | input[type="text"],
26 | input[type="file"],
27 | input[type="search"],
28 | input[type="password"],
29 | input[type="number"],
30 | input[type="email"],
31 | input[type="tel"],
32 | textarea,
33 | select{
34 | width:100%; height:@form-input-height; padding:@form-input-padding; border:1px solid @form-input-border-color; margin:@form-input-margin; border-radius:@form-input-border-radius; outline:none; line-height:1;
35 | &::-webkit-input-placeholder{ color:@form-input-placeholder;}
36 | }
37 | textarea{ height:auto;}
38 |
39 | .form-item{ margin:@form-item-margin; clear:both; user-select:none;}
40 | .help-block{ display:block; margin-bottom:10px; color:#999; font-size:1.2rem; clear:both;}
41 | .radio,
42 | .checkbox{
43 | // padding-left 为 21 px 是为了修复 radio 显示不全的问题。
44 | min-width:20px; margin-bottom:1.5rem; padding:0 20px 0 21px;
45 | input[type="radio"],
46 | input[type="checkbox"],
47 | .sq-checkbox,
48 | .sq-radio{ margin-left:-20px;}
49 | &.inline{ display:inline-block; float:left;}
50 | }
51 |
52 |
53 | // inline form
54 | // @desc: 行内表单布局
55 | // --------------------------------------------
56 | &.inline{
57 | display:flex; align-items:center; justify-content:space-between; overflow:hidden;
58 | select,
59 | input{ margin-bottom:0;}
60 | label{ margin:0 .5rem; padding-right:0; line-height:1;}
61 | input[type="submit"],
62 | input[type="button"]{
63 | width:100%;
64 | }
65 | .form-item{
66 | display:block; flex:1; padding-right:4px; margin-bottom:0;
67 | &:last-of-type{ padding-right:0;}
68 | }
69 | }
70 |
71 | // 水平表单布局
72 | &.horizontal{
73 | .form-item{
74 | //.display-box; .box-orient(horizontal); .box-align(center);
75 | display:flex; align-items:center; justify-content:space-between;
76 | &.align-top{
77 | //.box-align(start);
78 | justify-content:flex-start;
79 | label:first-of-type{ padding-top:4px;}
80 | }
81 | }
82 |
83 | label{ min-width:3em;}
84 |
85 | input[type="text"],
86 | input[type="search"],
87 | input[type="password"],
88 | input[type="number"],
89 | input[type="email"],
90 | input[type="tel"],
91 | textarea,
92 | select{
93 | display:block; width:100%; flex:1; margin-right:.5rem;
94 | &+input{ margin-left:.5rem;}
95 | &:last-of-type{ margin-right:0;}
96 | }
97 |
98 | .wd-4,
99 | .wd-6,
100 | .wd-8{ width:9em; text-align:right;}
101 | .wd-4{ width:5em;}
102 | .wd-6{ width:7em;}
103 | }
104 | }
105 |
106 |
107 | // Form
108 | // @desc: 优化表单
109 | // --------------------------------------------
110 | .@{ns}checkbox,
111 | .@{ns}radio{
112 | display:inline-block; position:relative; width:21px; height:21px; margin-right:.7rem; text-align:center; vertical-align:middle; overflow:hidden;
113 | & > span{ .overlay(); background:#fff; border:2px solid #ccc; border-radius:3px; text-indent:-999em;}
114 | input[type=checkbox],
115 | input[type=radio]{
116 | visibility: hidden;
117 | }
118 | input[type=checkbox]:checked + span {
119 | background:rgba(76,176,80,.9); border-color:#4CAF50; color:#fff; text-indent:0;
120 | }
121 | input[type=radio]:checked + span{
122 | background:rgba(76,176,80,.6); border-color:#4CAF50; color:#fff; text-indent:0;
123 | }
124 | }
125 | .@{ns}radio{
126 | & > span{ border-radius:@border-radius-circle;}
127 | }
--------------------------------------------------------------------------------
/doc_tpl/documentation.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel 文档
11 |
14 |
15 |
16 |
20 |
21 |
38 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/scripts/core/core.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.core
3 | * @version 1.0.0
4 | */
5 | /*global SQ*/
6 | window.SQ = {
7 | /**
8 | * 命名空间方法
9 | * @method
10 | * @name SQ.core.namespace
11 | * @param {string} nameSpaceString 命名空间字符串
12 | * @example
13 | * SQ.core.namespace('SQ.modules.module2');
14 | */
15 | namespace: function (nameSpaceString) {
16 | 'use strict';
17 | var parts = nameSpaceString.split('.');
18 | var parent = SQ;
19 | var i;
20 | if (parts[0] === 'SQ') {
21 | parts = parts.slice(1);
22 | } else {
23 | return false;
24 | }
25 | for (i = 0; i < parts.length; i += 1) {
26 | if (typeof parent[parts[i]]) {
27 | parent[parts[i]] = {};
28 | }
29 | parent = parent[parts[i]];
30 | }
31 | return parent;
32 | },
33 | /**
34 | * 判断对象类型
35 | * @example
36 | * SQ.core.isString(str);
37 | */
38 | isString: function (str) {
39 | 'use strict';
40 | return Object.prototype.toString.call(str) === '[object String]';
41 | },
42 | isArray: function (arr) {
43 | 'use strict';
44 | return Object.prototype.toString.call(arr) === '[object Array]';
45 | },
46 | isNumber: function (num) {
47 | 'use strict';
48 | return Object.prototype.toString.call(num) === '[object Number]';
49 | },
50 | isBoolean: function (bool) {
51 | 'use strict';
52 | return Object.prototype.toString.call(bool) === '[object Boolean]';
53 | },
54 | isNull: function (nullObj) {
55 | 'use strict';
56 | return Object.prototype.toString.call(nullObj) === '[object Null]';
57 | },
58 | isUndefined: function (undefinedObj) {
59 | 'use strict';
60 | return Object.prototype.toString.call(undefinedObj) === '[object Undefined]';
61 | },
62 | isFunction: function (fun) {
63 | 'use strict';
64 | return Object.prototype.toString.call(fun) === '[object Function]';
65 | },
66 | isObject: function (obj) {
67 | 'use strict';
68 | return Object.prototype.toString.call(obj) === '[object Object]';
69 | },
70 | /**
71 | * isJSON
72 | * 判断是否为 JSON 对象
73 | * @param string
74 | * @returns {boolean}
75 | * @see qatrix.js
76 | */
77 | // 暂时无法使用
78 | /*isJSON : function (string) {
79 | var rvalidchars = /^[\],:{}\s]*$/;
80 | var rvalidescape = /\\(?:['\\\/bfnrt]|u[\da-fA-F]{4})/g;
81 | var rvalidtokens = /'[^'\\\r\n]*'|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g;
82 | var rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g;
83 | return typeof string === 'string' && $.trim(string) !== '' ?
84 | rvalidchars.test(string
85 | .replace(rvalidescape, '@')
86 | .replace(rvalidtokens, ']')
87 | .replace(rvalidbraces, '')) :
88 | false;
89 | }*/
90 | /**
91 | * 继承
92 | * @param Child
93 | * @param Parent
94 | */
95 | extend: function (Child, Parent) {
96 | 'use strict';
97 | var F = function () {
98 | };
99 | F.prototype = Parent.prototype;
100 | Child.prototype = new F();
101 | Child.prototype.constructor = Child;
102 | Child.uber = Parent.prototype;
103 | },
104 | /**
105 | * 频率控制 返回函数连续调用时,fn 执行频率限定为每多少时间执行一次
106 | * @param fn {function} 需要调用的函数
107 | * @param delay {number} 延迟时间,单位毫秒
108 | * @param immediate {bool} 给 immediate 参数传递 false 绑定的函数先执行,而不是 delay 后后执行。
109 | * @return {function} 实际调用函数
110 | */
111 | throttle: function (fn, delay, immediate, debounce) {
112 | 'use strict';
113 | var curr;
114 | var lastCall = 0;
115 | var lastExec = 0;
116 | var timer = null;
117 | var diff; // 时间差
118 | var context; // 上下文
119 | var args;
120 | var exec = function () {
121 | lastExec = curr;
122 | fn.apply(context, args);
123 | };
124 | return function () {
125 | curr = + new Date();
126 | context = this;
127 | args = arguments;
128 | diff = curr - (debounce ? lastCall : lastExec) - delay;
129 | clearTimeout(timer);
130 | if (debounce) {
131 | if (immediate) {
132 | timer = setTimeout(exec, delay);
133 | } else if (diff >= 0) {
134 | exec();
135 | }
136 | } else {
137 | if (diff >= 0) {
138 | exec();
139 | } else if (immediate) {
140 | timer = setTimeout(exec, -diff);
141 | }
142 | }
143 | lastCall = curr;
144 | };
145 | },
146 | /**
147 | * 空闲控制 返回函数连续调用时,空闲时间必须大于或等于 delay,fn 才会执行
148 | * @param fn {function} 要调用的函数
149 | * @param delay {number} 空闲时间
150 | * @param immediate {bool} 给 immediate 参数传递 false 绑定的函数先执行,而不是 delay 后后执行。
151 | * @return {function} 实际调用函数
152 | */
153 | debounce: function (fn, delay, immediate) {
154 | 'use strict';
155 | return SQ.throttle(fn, delay, immediate, true);
156 | }
157 | };
--------------------------------------------------------------------------------
/example/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Example
11 |
12 |
13 |
14 |
15 | Squirrel Example
16 |
17 |
18 | - CSS
19 | -
20 |
21 | Button
22 |
23 |
24 | -
25 |
26 | Column
27 |
28 |
29 | -
30 |
31 | Form
32 |
33 |
34 | -
35 |
36 | Grid
37 |
38 |
39 |
40 |
41 | - Web 组件
42 | -
43 |
44 | Animate
45 |
46 |
47 | -
48 |
49 | Badge
50 |
51 |
52 | -
53 |
54 | Bar
55 |
56 |
57 | -
58 |
59 | Gallery
60 |
61 |
62 | -
63 |
64 | List
65 |
66 |
67 | -
68 |
69 | Icon
70 |
71 |
72 |
73 |
74 | - Javascript 插件
75 | -
76 |
77 | Dropdown
78 |
79 |
80 | -
81 |
82 | Lazyload
83 |
84 |
85 | -
86 |
87 | Loadmore
88 |
89 |
90 | -
91 |
92 | Panel
93 |
94 |
95 | -
96 |
97 | Modal
98 |
99 |
100 | -
101 |
102 | Screenshot
103 |
104 |
105 | -
106 |
107 | Sliber
108 |
109 |
110 | -
111 |
112 | Sticky
113 |
114 |
115 | -
116 |
117 | Tab
118 |
119 |
120 |
121 |
122 | - Theme 主题
123 | -
124 |
125 | Material
126 |
127 |
128 |
129 |
130 |
131 | Top
132 |
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/src/scripts/core/store.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.store
3 | * @version 1.1.0
4 | */
5 | /*global SQ*/
6 | SQ.store = {
7 | /**
8 | * Cookie
9 | * @example
10 | * Sq.cookie.set('name', 'value'); // 设置
11 | * Sq.cookie.get('name'); // 读取
12 | * Sq.cookie.del('name'); // 删除
13 | */
14 | cookie: {
15 | _getValue: function (offset) {
16 | 'use strict';
17 | var ck = document.cookie;
18 | var endstr = ck.indexOf(';', offset) === -1 ? ck.length : ck.indexOf(';', offset);
19 | return decodeURIComponent(ck.substring(offset, endstr));
20 | },
21 | get: function (key) {
22 | 'use strict';
23 | var me = this;
24 | var ck = document.cookie;
25 | var arg = key + '=';
26 | var argLen = arg.length;
27 | var cookieLen = ck.length;
28 | var i = 0;
29 | while (i < cookieLen) {
30 | var j = i + argLen;
31 | if (ck.substring(i, j) === arg) {
32 | return me._getValue(j);
33 | }
34 | i = ck.indexOf(' ', i) + 1;
35 | if (i === 0) {
36 | break;
37 | }
38 | }
39 | return null;
40 | },
41 | set: function (key, value) {
42 | 'use strict';
43 | var expdate = new Date();
44 | var year = expdate.getFullYear();
45 | var month = expdate.getMonth();
46 | var date = expdate.getDate() + 1;
47 | var argv = arguments;
48 | var argc = arguments.length;
49 | //获取更多实参,依次为:有效期、路径、域、加密安全设置
50 | var expires = (argc > 2) ? argv[2] : null;
51 | var path = (argc > 3) ? argv[3] : null;
52 | var domain = (argc > 4) ? argv[4] : null;
53 | var secure = (argc > 5) ? argv[5] : false;
54 |
55 | if (!!expires) {
56 | switch (expires) {
57 | case 'day':
58 | expdate.setYear(year);
59 | expdate.setMonth(month);
60 | expdate.setDate(date);
61 | expdate.setHours(8); // 补 8 小时时差
62 | expdate.setMinutes(0);
63 | expdate.setSeconds(0);
64 | break;
65 | case 'week':
66 | var week = 7 * 24 * 3600 * 1000;
67 | expdate.setTime(expdate.getTime() + week);
68 | break;
69 | default:
70 | expdate.setTime(expdate.getTime() + (expires * 1000 + 8 * 3600 * 1000));
71 | break;
72 | }
73 | }
74 |
75 | document.cookie = key + '=' + encodeURIComponent(value) + ((expires === null) ? '' : ('; expires=' + expdate.toGMTString())) +
76 | ((path === null) ? '' : ('; path=' + path)) + ((domain === null) ? '' : ('; domain=' + domain)) +
77 | ((secure === true) ? '; secure' : '');
78 | },
79 | del: function (key) {
80 | 'use strict';
81 | var me = this;
82 | var exp = new Date();
83 | exp.setTime(exp.getTime() - 1);
84 | var cval = me.get(key);
85 | document.cookie = key + '=' + cval + '; expires=' + exp.toGMTString();
86 | }
87 | },
88 | /**
89 | * localStorage
90 | */
91 | localStorage: {
92 | hasLoaclStorage: (function () {
93 | 'use strict';
94 | if(('localStorage' in window) && window.localStorage !== null) {
95 | return true;
96 | }
97 | }()),
98 | // expires 过期时间,单位 min
99 | get: function (key, expires) {
100 | 'use strict';
101 | var me = this;
102 | var now = new Date().getTime();
103 | var localData;
104 | var time;
105 | var dataStore;
106 |
107 | if (!key || !me.hasLoaclStorage) {
108 | return;
109 | }
110 |
111 | localData = JSON.parse(localStorage.getItem(key));
112 |
113 | if (localData) {
114 | time = localData.time;
115 | dataStore = localData.dataStore;
116 |
117 | // 填写了 expires 过期时间
118 | if (expires) {
119 | dataStore = parseInt(expires, 10) * 1000 * 60 > (now - parseInt(time, 10)) ? dataStore : false;
120 | }
121 | return dataStore;
122 | }
123 | },
124 | set: function (key, value) {
125 | 'use strict';
126 | var me = this;
127 | var ds = {};
128 | var now = new Date().getTime();
129 | if (!key || !value || !me.hasLoaclStorage) {
130 | return;
131 | }
132 |
133 | ds.dataStore = value;
134 | ds.time = now;
135 | ds = JSON.stringify(ds);
136 |
137 | localStorage.setItem(key, ds);
138 | },
139 | del: function (key) {
140 | 'use strict';
141 | var me = this;
142 | if (!key || !me.hasLoaclStorage) {
143 | return;
144 | }
145 | localStorage.removeItem(key);
146 | },
147 | clearAll: function () {
148 | 'use strict';
149 | var me = this;
150 | if (!me.hasLoaclStorage) {
151 | return;
152 | }
153 | localStorage.clear();
154 | }
155 | }
156 | };
157 |
--------------------------------------------------------------------------------
/src/scripts/dropdown.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.Dropdown 按钮插件
3 | * @version 0.6.0
4 | */
5 |
6 | /**
7 | * @changelog
8 | * 0.6.6 * 更名为 Dropdown
9 | * 0.5.0 * 重写插件,调用方式改为 $. 链式调用。
10 | * 0.2.0 * 重写 menu 模式代码,独立 button.js 为插件
11 | * 0.1.2 * 修复 jshint 问题
12 | * 0.1.1 + 新增 menu 交互模式
13 | * 0.0.1 + 新建
14 | */
15 | /*global $, SQ, console, jQuery*/
16 | (function ($) {
17 | 'use strict';
18 | /**
19 | * @name Dropdown
20 | * @classdesc 按钮交互插件
21 | * @constructor
22 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性)
23 | * @param {string} config.ANIMATE 动画类,例如 .fadeIn
24 | * @param {string} config.EVE_EVENT_TYPE 交互触发方式,默认为 'click'
25 | * @param {string} config.MODE 按钮交互模式,默认为 'menu'
26 | * @example $('.J_dropdown').dropdown({
27 | ANIMATE: '.fadeIn quick'
28 | });
29 | */
30 |
31 | var scope = 'sq-dropdown'; // data-* 后缀
32 | var defaults = {
33 | MODE: 'menu',
34 | EVE_EVENT_TYPE: 'click'
35 | };
36 |
37 | function Dropdown ( element, options ) {
38 | this.element = element;
39 | this.settings = $.extend( {}, defaults, options );
40 | this._defaults = defaults;
41 | this.init();
42 | }
43 |
44 | Dropdown.prototype = {
45 | construtor: 'Dropdown',
46 | init: function () {
47 | var me = this;
48 | var date = new Date().getTime().toString().slice(-4);
49 | me.$element = $(me.element);
50 | me.elementClassName = me.settings.selector.slice(1); // '.style-name' => 'style-name'
51 |
52 | // _documentEvent 判断时会碰到相同 className 的情况,会导致无法隐藏菜单
53 | me.classId = scope + '-id-' + date;
54 | me.$element.addClass(me.classId);
55 | if (me.settings.MODE === 'menu') {
56 | me.menu();
57 | }
58 | },
59 | setState: function (state) {
60 | var me = this;
61 | if (state === 'active') {
62 | me.$element.addClass('active');
63 | }
64 | if (state === 'init') {
65 | me.$element.removeClass('active');
66 | }
67 | },
68 | menu: function () {
69 | var me = this;
70 | var $doc = $(document);
71 | var $allButtons = $(me.settings.selector);
72 | var $allMenus = $allButtons.find('.dropdown-content');
73 | var $menu = me.$element.find('.dropdown-content');
74 |
75 | me.$element.on(me.settings.EVE_EVENT_TYPE + '.sq.dropdown', function () {
76 | if (!me.$element.hasClass('active')) {
77 | _showMenu();
78 | } else {
79 | _hideMenu();
80 | }
81 | });
82 |
83 | function _showMenu() {
84 | //** reset all menus
85 | $allMenus.hide();
86 | $allButtons.removeClass('active');
87 | //** add animate
88 | if (me.settings.ANIMATE) {
89 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE;
90 | $menu.addClass('animated ' + animateClassName);
91 | }
92 | $menu.show();
93 | me.setState('active');
94 | $doc.on('click.sq.dropdown', _documentEvent);
95 | }
96 |
97 | function _hideMenu() {
98 | $menu.hide();
99 | me.setState('init');
100 | $doc.off('click.sq.dropdown', _documentEvent);
101 | }
102 |
103 | function _documentEvent(e) {
104 | if (!$(e.target).parents('.' + scope).hasClass(me.classId)) {
105 | _hideMenu();
106 | }
107 | }
108 | }
109 | };
110 |
111 | $.fn.dropdown = function ( options ) {
112 | var isZepto = typeof Zepto !== 'undefined' ? true : false;
113 | var isJQuery = typeof jQuery !== 'undefined' ? true : false;
114 | var plugin;
115 |
116 | if (SQ.isObject(options)) {
117 | options = options || {};
118 | // 当使用 $(this).modal({...}) 调用时,无法获取 this.selector 值,
119 | // 所以去手动获取该 DOM 的类名
120 | options.selector = this.selector || '.' + this[0].className.split(' ').join('.');
121 | }
122 |
123 | this.each(function() {
124 | if (isJQuery) {
125 | if (!$.data(this, scope + 'init')) {
126 | $.data( this, scope + 'init', new Dropdown( this, options ) );
127 | }
128 | } else if (isZepto) {
129 | if (!$(this).data(scope + 'init')) {
130 | plugin = new Dropdown( this, options );
131 | $(this).data(scope + 'init', 'initialized');
132 | }
133 | }
134 | });
135 | // chain jQuery functions
136 | return this;
137 | };
138 |
139 | // DATA-API
140 | // ===============
141 | /*$(document).on('click.button.data.api', '[data-'+ scope +']', function () {
142 | var $me = $(this);
143 | var pluginSetting = $me.data(scope);
144 | //console.log(pluginSetting)
145 | $me.button(pluginSetting);
146 | });*/
147 | /*$(document).ready(function () {
148 | $('[data-'+ scope +']').each(function () {
149 | var $me = $(this);
150 | var pluginSetting = $me.data(scope);
151 | if (SQ.isObject(pluginSetting)) {
152 | $me.button(pluginSetting);
153 | }
154 | });
155 | });*/
156 | })($);
--------------------------------------------------------------------------------
/example/bar.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Example Bars
11 |
12 |
13 |
14 |
28 |
29 |
30 |
工具栏
31 |
32 |
40 |
41 |
47 |
48 |
49 |
导航栏
50 |
51 |
52 | Ringtone
53 |
54 |
55 |
56 |
57 | 类别
58 |
64 | 清单
65 |
66 |
67 |
68 |
分页栏
69 |
79 |
80 |
81 |
选项栏
82 |
88 |
89 |
95 |
搜索栏
96 |
102 |
103 |
109 |
110 |
115 |
116 |
117 | Top
118 |
119 |
120 |
121 |
122 |
123 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Squirrel-3
3 | * @version 1.0.1
4 | * @anthor: iinterest.bell@gmail.com
5 | */
6 | /* jshint strict: false, quotmark: false */
7 |
8 | /**
9 | * change log
10 | * 1.0.1 * autoprefixer 设置为 'android 4', 'ios 7'
11 | * - 删除 gulp-minify-css
12 | */
13 |
14 | // Load plugins
15 | var gulp = require('gulp');
16 | var autoprefixer = require('gulp-autoprefixer');
17 | var concat = require('gulp-concat');
18 | var del = require('del');
19 | var footer = require('gulp-footer');
20 | var gulpif = require('gulp-if');
21 | var jshint = require('gulp-jshint');
22 | var less = require('gulp-less');
23 | var livereload = require('gulp-livereload');
24 | var notify = require('gulp-notify');
25 | var plumber = require('gulp-plumber');
26 | var rename = require('gulp-rename');
27 | var replace = require('gulp-replace');
28 | var uglify = require('gulp-uglify');
29 | var sourcemaps = require('gulp-sourcemaps');
30 |
31 | var pkg = require('./package.json');
32 | var date = new Date();
33 | pkg.date = date.getFullYear() + '-' + ('00' + (date.getMonth() + 1)).slice(-2) + '-' +
34 | ('00' + date.getDate()).slice(-2) + ' ' + ('00' + date.getHours()).slice(-2) + ':' +
35 | ('00' + date.getMinutes()).slice(-2) + ':' + ('00' + date.getSeconds()).slice(-2);
36 | var timestamp = ['\n/*',
37 | ' <%= pkg.name %>',
38 | ' @version v<%= pkg.version %>',
39 | ' @date <%= pkg.date %>',
40 | ' */'].join('');
41 |
42 | // Config
43 | var toggle = {
44 | autoprefix: true,
45 | imagemin: false,
46 | jshint: true,
47 | sourcemaps: true
48 | };
49 | var paths = {
50 | scripts: [
51 | 'src/scripts/core/*.js',
52 | 'src/scripts/global.js',
53 | 'src/scripts/*.js',
54 | 'src/scripts/plugins/*.js',
55 | 'src/scripts/**/*.js'
56 | ],
57 | styles: 'src/styles/' + pkg.name + '.less',
58 | images: [
59 | 'src/images/**/*.jpg',
60 | 'src/images/**/*.gif',
61 | 'src/images/**/*.png'
62 | ],
63 | fonts: [
64 | /*'bower_components/fontawesome/fonts/fontawesome.otf',
65 | 'bower_components/fontawesome/fonts/fontawesome-webfont.eot',
66 | 'bower_components/fontawesome/fonts/fontawesome-webfont.svg',*/
67 | 'bower_components/fontawesome/fonts/fontawesome-webfont.ttf',
68 | 'bower_components/fontawesome/fonts/fontawesome-webfont.woff'
69 | ],
70 | lib: [
71 | 'bower_components/jquery/dist/jquery.min.js',
72 | 'bower_components/jquery/dist/jquery.min.map'
73 | ],
74 | watchs: {
75 | scripts: 'src/scripts/**/*.js',
76 | styles: 'src/styles/**/*.less',
77 | templates: 'example/**/*.html',
78 | dist: 'dist/**/*'
79 | }
80 | };
81 | var dests = {
82 | styles: './dist/css',
83 | scripts: './dist/js',
84 | images: './dist/images',
85 | fonts: './dist/fonts',
86 | maps: '../maps'
87 | };
88 | var options = {
89 | autoprefix: {
90 | browsers: ['android 4', 'ios 7']
91 | },
92 | fileName: pkg.name,
93 | less: {compress: true},
94 | imagemin: {progressive: true},
95 | jshint: '.jshintrc',
96 | jshintReporter: 'default',
97 | rename: {suffix: '.min'}
98 | };
99 |
100 | // Tasks
101 | // 通常情况,以下任务是不能修改的,如果遇到特殊情况需要修改,请注明!
102 |
103 | // Styles
104 | gulp.task('styles', function () {
105 | gulp.src(paths.styles)
106 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
107 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.init()))
108 | .pipe(less(options.less))
109 | .pipe(gulpif(toggle.autoprefix, autoprefixer(options.autoprefix)))
110 | .pipe(rename(options.rename))
111 | .pipe(footer(timestamp, {pkg: pkg}))
112 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.write(dests.maps)))
113 | .pipe(gulp.dest(dests.styles));
114 | });
115 |
116 | // Scripts
117 | gulp.task('scripts', function () {
118 | gulp.src(paths.scripts)
119 | .pipe(plumber({errorHandler: notify.onError('Error: <%= error.message %>')}))
120 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.init()))
121 | .pipe(gulpif(toggle.jshint, jshint(options.jshint)))
122 | .pipe(gulpif(toggle.jshint, jshint.reporter(options.jshintReporter)))
123 | .pipe(concat(options.fileName + '.js'))
124 | .pipe(rename(options.rename))
125 | .pipe(uglify())
126 | .pipe(footer(timestamp, {pkg: pkg}))
127 | .pipe(gulpif(toggle.sourcemaps, sourcemaps.write(dests.maps)))
128 | .pipe(gulp.dest(dests.scripts));
129 | });
130 |
131 | // Images
132 | gulp.task('images', function () {
133 | gulp.src(paths.images)
134 | .pipe(gulp.dest(dests.images));
135 | });
136 |
137 | // Fonts
138 | gulp.task('coypFonts', function () {
139 | gulp.src(paths.fonts)
140 | .pipe(gulp.dest(dests.fonts));
141 | });
142 |
143 | // Lib
144 | gulp.task('copyLib', function () {
145 | gulp.src(paths.lib)
146 | .pipe(gulp.dest(dests.scripts));
147 | });
148 |
149 | // Clean
150 | gulp.task('clean', function (cb) {
151 | del(['dist', 'build'], cb);
152 | });
153 |
154 | // Build
155 | gulp.task('build', ['clean'], function () {
156 | gulp.start('styles', 'scripts', 'images', 'coypFonts', 'copyLib');
157 | });
158 |
159 | // Default
160 | gulp.task('default', ['build']);
161 |
162 | // Release 发布
163 | gulp.task('release', function (cb) {
164 | // 因为 UAE 不支持 .map 文件类型,所以暂时在发布时去除 sourcemaps
165 | toggle.sourcemaps = false;
166 | gulp.start('build');
167 | });
168 |
169 | // Watch
170 | gulp.task('watch', function () {
171 | // .js files
172 | gulp.watch(paths.watchs.scripts, ['scripts']);
173 | // .less files
174 | gulp.watch(paths.watchs.styles, ['styles']);
175 | // images files
176 | gulp.watch(paths.images, ['images']);
177 | // LiveReload server
178 | livereload.listen();
179 | // Watch any files in dist/, pages/, reload on change
180 | gulp.watch([paths.watchs.dist, paths.watchs.templates]).on('change', livereload.changed);
181 | });
--------------------------------------------------------------------------------
/example/sticky.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Sticky
11 |
12 |
13 |
14 |
15 |
16 | Sticky
17 |
25 |
26 |
136 |
137 | Top
138 |
139 |
140 |
141 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/src/scripts/lazyload.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.LazyLoad 延迟加载插件
3 | * @version 1.0.3
4 | */
5 |
6 | /**
7 | * @changelog
8 | * 1.0.3 * data-img 修改为 data-original
9 | * 1.0.2 * 修改 scroll 绑定事件,使用新增的节流函数,精简函数。
10 | * 1.0.1 * 增加验证提示,调整了 init 函数。
11 | * 1.0.0 * 重写插件,调用方式改为 $. 链式调用。
12 | * 0.8.1 * 新增 ANIMATE 设置。
13 | * 0.8.0 * 重写 lazylaod 插件,提高整体性能。
14 | * 0.7.0 * 调整滑动阀值 scrollDelay,由 200 调整至 150;
15 | * * 调整可视区的计算方式,由 offset 改为 getBoundingClientRect;
16 | * * 针对 UC 浏览器极速版进行优化,可以在滑动过程中进行加载。
17 | * 0.6.5 * 修复 jshint 问题。
18 | * 0.6.4 * 修复图片加载失败时会导致 error 时间一直被触发的 bug;
19 | * 修复与 loadmore 插件配合使用时,无法替换加载错误的图片。
20 | * 0.6.3 + 新增首屏图片自动加载功能;
21 | * + 新增占位图、占位背景设置。
22 | * 0.6.0 + 首屏图片自动加载。
23 | * 0.5.1 * 完成图片模式的延迟加载功能。
24 | * 0.0.1 + 新建。
25 | */
26 | /*global $, SQ, console, jQuery */
27 | (function ($) {
28 | 'use strict';
29 | /**
30 | * @name LazyLoad
31 | * @classdesc 内容延迟加载
32 | * @constructor
33 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性)
34 | * @param {string} config.ANIMATE 动画类,例如 .fadeIn
35 | * @param {string} config.IMG_PLACEHOLDER 占位图片
36 | * @param {string} config.MODE 延迟加载模式,默认为:image(图片模式)
37 | * @param {number} config.NUM_THRESHOLD 灵敏度,数值越大越灵敏,延迟性越小,默认为 200
38 | *
39 | * @example $('.J_lazyload').lazyload({
40 | ANIMATE: '.fadeIn'
41 | });
42 | */
43 |
44 | var scope = 'sq-lazyload'; // data-* 后缀
45 | var defaults = {
46 | MODE: 'image',
47 | NUM_THRESHOLD: 350,
48 | IMG_PLACEHOLDER: ''
49 | };
50 |
51 | function Lazyload ( element, options ) {
52 | this.element = element;
53 | this.settings = $.extend( {}, defaults, options );
54 | this._defaults = defaults;
55 | this.init();
56 | }
57 |
58 | Lazyload.prototype = {
59 | construtor: 'Lazyload',
60 | scrollTimer: 0, // 滑动计时器
61 | scrollDelay: 150, // 滑动阀值
62 | init: function () {
63 | var me = this;
64 | me.$element = $(me.element);
65 | me.elementClassName = me.settings.selector.slice(1); // '.style-name' => 'style-name'
66 | me.$element.attr('src', me.settings.IMG_PLACEHOLDER);
67 | if (me._verify()) {
68 | me._bindLazyEvent();
69 | me._trigger();
70 | me._loadImg();
71 | }
72 | },
73 | /**
74 | * 验证参数是否合法
75 | * @returns {boolean}
76 | * @private
77 | */
78 | _verify: function () {
79 | /*if (!this.$element.length) {
80 | console.warn('SQ.lazyload: '+ this.settings.selector +'下未找到');
81 | return false;
82 | }*/
83 | return true;
84 | },
85 | _bindLazyEvent: function () {
86 | var me = this;
87 | // 为延迟加载元素绑定一次性执行事件
88 | me.$element.one('appear', function () {
89 | var img = this;
90 | var $img = $(img);
91 | var src = $img.attr('data-original');
92 | // 替换 src 操作
93 | if (src) {
94 | $img.addClass('unvisible').attr('src', src).removeAttr('data-original').removeClass(me.elementClassName);
95 | $img.on('load', function () {
96 | // 添加动画
97 | if (me.settings.ANIMATE) {
98 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE;
99 | $img.addClass('animated ' + animateClassName);
100 | }
101 | $img.removeClass('unvisible');
102 | });
103 | $img.on('error', function () {
104 | if (me.settings.IMG_PLACEHOLDER) {
105 | $(this).attr('src', me.settings.IMG_PLACEHOLDER).off('error');
106 | }
107 | });
108 | }
109 | });
110 | },
111 | _trigger: function () {
112 | var me = this;
113 | $(window).on('scroll.bs.lazyload', SQ.throttle(function () {
114 | if (me.settings.MODE === 'image') {
115 | me._loadImg();
116 | }
117 | }, me.scrollDelay));
118 | },
119 | /**
120 | * 判断是否在显示区域
121 | */
122 | _isInDisplayArea: function (item) {
123 | var me = this;
124 | if (item.getBoundingClientRect()) {
125 | var pos = item.getBoundingClientRect();
126 | return pos.top > 0 - me.settings.NUM_THRESHOLD && pos.top - me.settings.NUM_THRESHOLD < window.innerHeight;
127 | } else {
128 | var $item = $(item);
129 | var winH = window.innerHeight;
130 | var winOffsetTop = window.pageYOffset; // window Y 轴偏移量
131 | var itemOffsetTop = $item.offset().top;
132 | // itemOffsetTop >= winOffsetTop 只加载可视区域下方的内容
133 | // winOffsetTop + winH + me.settings.NUM_THRESHOLD 加载可视区域下方一屏内的内容
134 | return itemOffsetTop >= winOffsetTop && itemOffsetTop <= winOffsetTop + winH + me.settings.NUM_THRESHOLD;
135 | }
136 | },
137 | _loadImg: function () {
138 | var me = this;
139 | if (me.settings.IMG_PLACEHOLDER && me.$element.hasClass(me.elementClassName)) {
140 | //me.$element.attr('src', me.settings.IMG_PLACEHOLDER);
141 | me.$element.on('error', function () {
142 | $(this).attr('src', me.settings.IMG_PLACEHOLDER).off('error');
143 | });
144 | }
145 | if (me._isInDisplayArea(me.$element.get(0))) {
146 | me.$element.trigger('appear');
147 | }
148 | }
149 | };
150 |
151 | $.fn.lazyload = function ( options ) {
152 | var isZepto = typeof Zepto !== 'undefined' ? true : false;
153 | var isJQuery = typeof jQuery !== 'undefined' ? true : false;
154 | var plugin;
155 |
156 | options = options || {};
157 | options.selector = this.selector;
158 |
159 | /*if (!this.length) {
160 | console.warn('SQ.lazyload: 未找到'+ this.selector +'元素');
161 | }*/
162 |
163 | this.each(function() {
164 | if (isJQuery) {
165 | if ( !$.data( this, scope ) ) {
166 | $.data( this, scope, new Lazyload( this, options ) );
167 | }
168 | } else if (isZepto) {
169 | if (!$(this).data(scope)) {
170 | plugin = new Lazyload( this, options );
171 | $(this).data(scope, 'initialized');
172 | }
173 | }
174 | });
175 | // chain jQuery functions
176 | return this;
177 | };
178 |
179 | })($);
--------------------------------------------------------------------------------
/example/button.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Example Button
11 |
12 |
13 |
14 |
15 |
16 | Button
17 |
28 |
29 |
30 |
按钮
31 |
32 |
Anchor
33 |
Div
34 |
35 |
36 |
37 |
38 |
39 |
尺寸
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
62 |
63 |
64 |
65 |
禁用
66 |
70 |
71 |
72 |
73 |
按钮组
74 |
87 |
88 |
95 |
96 |
97 |
98 |
图标
99 |
103 |
108 |
112 |
117 |
122 |
123 |
124 |
125 |
样式
126 |
131 |
136 |
140 |
141 |
142 | Top
143 |
144 |
145 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/example/material.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Squirrel Material
10 |
11 |
12 |
13 |
14 |
22 | Material
23 |
24 |
25 |
26 |
27 |
28 |
Welcome back
29 |
It's been a while.
Read and new books lately?
30 |
31 |
35 |
36 |
37 |
38 |
47 |
55 |
56 |
Each of these cards contains a unique data set:
57 | a checklist with an action, a note with an action, a note with a photo.
58 |
59 |
63 |
64 |
65 | 单行文本
66 |
80 |
81 |
102 |
103 | 多行文本
104 |
160 |
161 |
162 |
163 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
--------------------------------------------------------------------------------
/example/lazyload.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Lazyload
11 |
12 |
13 |
14 |
15 |
16 | Lazyload
17 |
25 |
26 |
136 |
137 | Top
138 |
139 |
140 |
141 |
146 |
147 |
148 |
--------------------------------------------------------------------------------
/src/styles/variables.less:
--------------------------------------------------------------------------------
1 | //
2 | // Global variables
3 | // 全局变量
4 | // ---------------------------------------------------------------------------
5 |
6 |
7 | // ===========================================================================
8 | // Common
9 | // ===========================================================================
10 |
11 |
12 | // 基本设置 Base Setting
13 | // --------------------------------------------
14 | @ns: ~"sq-";
15 | //** 圆角 Radius
16 | @border-radius-circle: 999rem;
17 | @border-radius-base: 3px;
18 | //** 颜色 color
19 | @black: #333333;
20 | @white: #ffffff;
21 | //** 其他 Other
22 | @shardow-normal: 0 1px 6px rgba(0,0,0,.12);
23 |
24 |
25 | // 文字设置 Font
26 | // --------------------------------------------
27 | @font-family: "Segoe UI", Helvetica, "Lucida Grande", Arial, "Microsoft YaHei", FreeSans, Arimo, "Droid Sans","wenquanyi micro hei","Hiragino Sans GB", "Hiragino Sans GB W3", Arial, sans-serif;
28 | @default-font-color: @black;
29 | @default-font-size: 1.6rem; // 16px
30 | @default-line-height: 1.428571429; // @see bootstrap
31 | @head-font-size: 1.8rem; // 主导航文字
32 | @title-font-size: 1.6rem; // 标题文字
33 | @content-font-size: 1.4rem; // 内容文字
34 | @form-base-font-size: 1.4rem; // 表单文字
35 | @description-font-size: 1.2rem; // 描述文字
36 | @badge-font-size: 1.2rem; // 徽标文字
37 |
38 |
39 | // 媒体查询 Media queries
40 | // --------------------------------------------
41 | @screen: ~"only screen";
42 | @landscape: ~"(orientation: landscape)";
43 | @portrait: ~"(orientation: portrait)";
44 | //** Mobile size
45 | @mobile-max-size: 640px; // 0 - 640px
46 | @mobile-landscape-size: ~"@{landscape}";
47 | @mobile-portrait-size: ~"@{portrait}";
48 | //** Table size
49 | @table-min-size: 768px; // 768 - ∞
50 | @table-landscape-size: ~"@{landscape}";
51 | @table-portrait-size: ~"@{portrait}";
52 | //** Mobile screen
53 | @mobile-screen: ~"@{screen}";
54 | @mobile-screen-landscape: ~"@{screen} and (orientation: landscape)";
55 | @mobile-screen-portrait: ~"@{screen} and (orientation: portrait)";
56 | //** Table screen
57 | @table-screen: ~"@{screen} and (min-width: @{table-min-size})";
58 | @table-screen-landscape: ~"@{screen} and (min-width: @{table-min-size}) and (@{table-landscape-size})";
59 | @table-screen-portrait: ~"@{screen} and (min-width: @{table-min-size}) and (@{table-portrait-size})";
60 |
61 |
62 | // ===========================================================================
63 | // CSS
64 | // ===========================================================================
65 |
66 |
67 | // 按钮 Button
68 | // --------------------------------------------
69 | @btn-border-radius: @border-radius-base;
70 | //** normal (sq-btn height = 9*2 + 16 + 2 = 36px)
71 | @btn-padding-normal: 9px 1rem;
72 | //** large
73 | @btn-padding-large: 12px 1rem;
74 | @btn-font-size-large: 1.8rem;
75 | //** small
76 | @btn-padding-small: 6px 1rem;
77 | @btn-font-size-small: 1.4rem;
78 | //** mini
79 | @btn-padding-mini: 4px 1rem;
80 | @btn-font-size-mini: 1.2rem;
81 | @btn-disabled-opacity: .7;
82 |
83 |
84 | // 表单 Form
85 | // --------------------------------------------
86 | @form-label-margin: 0 0 5px 0;
87 | @form-radio-checkbox-margin:4px .7rem 0 0;
88 | //** input
89 | @form-input-height: 36px;
90 | @form-input-margin: 0 0 5px 0;
91 | @form-input-padding: 4px 6px;
92 | @form-input-border-color: #ccc;
93 | @form-input-border-radius: @border-radius-base;
94 | @form-input-placeholder: #999;
95 | @form-item-margin: 0 0 1.5rem 0;
96 |
97 |
98 | // 栅格系统 Grid system
99 | // --------------------------------------------
100 | @grid-columns: 12;
101 | @gutter: 1.5rem; // 15px
102 | @grid-gutter-width: 3rem; // 30px
103 |
104 |
105 | // ===========================================================================
106 | // Components
107 | // ===========================================================================
108 |
109 |
110 | // 徽章 Badge
111 | // --------------------------------------------
112 | @badge-border-radius: 10px;
113 | @badge-padding: 3px 7px;
114 | @badge-black: #444444;
115 | @badge-blue: #009bd8;
116 | @badge-green: #6eb82c;
117 | @badge-grey: #a9a7a7;
118 | @badge-red: #e34541;
119 | @badge-orange: #faa800;
120 |
121 |
122 | // 栏条 Bar
123 | // --------------------------------------------
124 | @bar-normal-height: 49px;
125 | @bar-toolbar-padding: 0 4px;
126 | @bar-toolbar-index: 100;
127 | @bar-nav-extend-margin: 0 .5rem;
128 | @bar-title-font-size: @head-font-size;
129 | @bar-pagination-font-size: @content-font-size;
130 | @bar-tab-font-size: @content-font-size;
131 | @bar-search-padding: 0 8px;
132 | @bar-search-label-bg: @white;
133 | @bar-search-height: 32px;
134 | @bar-search-icon: ~"\f002";
135 | @bar-search-btn-space: 4px;
136 |
137 |
138 | // 列表 List
139 | // --------------------------------------------
140 | @list-font-size: @content-font-size;
141 | //@list-icon-width: 60px;
142 | //@list-icon-height: 60px;
143 | @list-padding: 12px; // 不能使用 top right bottom left 格式
144 | @list-line-height: 1.4;
145 | @list-badge-pos-top: -5px;
146 | @list-badge-pos-left: -5px;
147 | @list-overlay-zindex: 11;
148 | @list-inside-space: .5rem;
149 | @list-multi-border-color: #e5e5e5;
150 | @list-tile-padding: 10px 5px;
151 |
152 |
153 | // 画廊 Gallery
154 | // --------------------------------------------
155 | @gallery-space: 3px;
156 | @gallery-title-font-size: 1.4rem;
157 | @gallery-title-font-desc: 1.4rem;
158 | @gallery-overlay-title-bg: rgba(0,0,0,.6);
159 | @gallery-overlay-title-color:@white;
160 | @gallery-item-min-height: 100px;
161 |
162 |
163 | // 字体图标 Icon
164 | // --------------------------------------------
165 | @icon-font-family: "FontAwesome";
166 | @icon-font-path: "../fonts/";
167 | @icon-font-name: "fontawesome-webfont";
168 | @icon-x2-size: 2rem;
169 | @icon-x3-size: 2.7rem;
170 | @icon-x4-size: 4rem;
171 | @icon-x5-size: 5rem;
172 |
173 |
174 | // =============================================================================
175 | // Plugins
176 | // =============================================================================
177 |
178 |
179 | // Dropdown
180 | // --------------------------------------------
181 | @dropdown-content-bg: @white;
182 | @dropdown-content-radius: @border-radius-base;
183 | @dropdown-border-color: #e5e5e5;
184 | @dropdown-hover: #efefef;
185 | @dropdown-index: 100;
186 | @dropdown-content-padding: 7px 10px;
187 | @dropdown-min-width: 100px;
188 |
189 |
190 | // Loadmore
191 | // --------------------------------------------
192 | @loadmore-lin-height: @default-line-height;
193 | @loadmore-padding: 8px;
194 |
195 |
196 | // Panel
197 | // --------------------------------------------
198 | @panel-bg: #333;
199 |
200 |
201 | // Modal
202 | // --------------------------------------------
203 | @modal-bg: @white;
204 | @modal-radius: @border-radius-base; // 不能使用 top right bottom left 格式
205 | @modal-border-color: #d7d7d7;
206 | @modal-btn-padding: 8px;
--------------------------------------------------------------------------------
/example/loadmore.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Loadmore
11 |
12 |
13 |
14 |
15 |
16 | Loadmore
17 |
25 |
26 |
136 |
137 | Top
138 |
139 |
140 |
141 |
172 |
173 |
174 |
--------------------------------------------------------------------------------
/example/animate.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | Squirrel Animate
11 |
12 |
13 |
14 |
15 |
16 | Animate
17 |
25 |
26 |
27 |
28 |
31 |
32 |
引起注意(Attention Seekers)
33 | bounce
34 | flash
35 | pulse
36 | rubberBand
37 | shake
38 | swing
39 | tada
40 | wobble
41 | 弹入(Bouncing Entrances)
42 | bounceIn
43 | bounceInDown
44 | bounceInLeft
45 | bounceInRight
46 | bounceInUp
47 | 弹出(Bouncing Exits)
48 | bounceOut
49 | bounceOutDown
50 | bounceOutLeft
51 | bounceOutRight
52 | bounceOutUp
53 | 淡入(Fading Entrances)
54 | fadeIn
55 | fadeInDown
56 | fadeInDownBig
57 | fadeInLeft
58 | fadeInLeftBig
59 | fadeInRight
60 | fadeInRightBig
61 | fadeInUp
62 | fadeInUpBig
63 | 淡出(Fading Exits)
64 | fadeOut
65 | fadeOutDown
66 | fadeOutDownBig
67 | fadeOutLeft
68 | fadeOutLeftBig
69 | fadeOutRight
70 | fadeOutRightBig
71 | fadeOutUp
72 | fadeOutUpBig
73 | Flippers
74 | flip
75 | flipInX
76 | flipInY
77 | flipOutX
78 | flipOutY
79 | Lightspeed
80 | lightSpeedIn
81 | lightSpeedOut
82 | Rotating Entrances
83 | rotateIn
84 | rotateInDownLeft
85 | rotateInDownRight
86 | rotateInUpLeft
87 | rotateInUpRight
88 | Rotating Exits
89 | rotateOut
90 | rotateOutDownLeft
91 | rotateOutDownRight
92 | rotateOutUpLeft
93 | rotateOutUpRight
94 | Specials
95 | hinge
96 | rollIn
97 | rollOut
98 | Zoom Entrances
99 | zoomIn
100 | zoomInDown
101 | zoomInLeft
102 | zoomInRight
103 | zoomInUp
104 | Zoom Exits
105 | zoomOut
106 | zoomOutDown
107 | zoomOutLeft
108 | zoomOutRight
109 | zoomOutUp
110 |
111 |
112 |
113 | Top
114 |
115 |
116 |
117 |
118 |
119 |
--------------------------------------------------------------------------------
/src/scripts/sticky.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.Sticky 悬停插件
3 | * @version 1.6.0
4 | */
5 |
6 | /**
7 | * @changelog
8 | * 1.6.0 * 插件更名为 SQ.Sticky
9 | * 1.5.1 * 修复 $placeholderDom 高度计算问题
10 | * 1.5.0 * 重写插件,调用方式改为 $. 链式调用。
11 | * 1.0.0 + 新增 refresh 方法,可以刷新 Fixed 列表;
12 | * * 更改 ARRY_FIXED_POSITION 默认值,修正 fixed 元素高度时会占据全屏的 bug;
13 | * * 修正 triggerPosTop 没有将 scrollY 的值计算在内的 bug。
14 | * 0.9.0 * 完成主要功能
15 | * 0.0.1 + 新建。
16 | */
17 | /*global $, SQ, console, jQuery */
18 | (function ($) {
19 | 'use strict';
20 | /**
21 | * @name Sticky
22 | * @classdesc 元素固定定位
23 | * @constructor
24 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性)
25 | * @param {string} config.ANIMATE 动画类,默认值:undefined
26 | * @param {array} config.ARRY_FIXED_POSITION 固定位置设置,遵循 [上,右,下,左] 规则,默认为:[0, 0, 'auto', 0]
27 | * @param {number} config.NUM_TRIGGER_POSITION 设置 sticky 激活位置,当有该值时以该值为准,没有则以元素当前位置为准
28 | * @param {number} config.NUM_ZINDEX z-index 值设置,默认为 101
29 | * @param {boolen} config.PLACEHOLD 是否设置占位 DOM,默认为 false
30 | * @param {function} config.fixedIn 设置固定布局时回调函数
31 | * @param {function} config.fixedOut 取消固定布局时回调函数
32 | * @example $('.J_fixedHeader').sticky({
33 | PLACEHOLD: true
34 | });
35 | */
36 |
37 | var scope = 'sq-sticky'; // data-* 后缀
38 | var defaults = {
39 | ARRY_FIXED_POSITION: [0, 0, 'auto', 0],
40 | NUM_ZINDEX: 101, // .sq-header 的 z-index 值为 100
41 | PLACEHOLD: false
42 | };
43 |
44 | function Sticky ( element, options ) {
45 | this.element = element;
46 | this.settings = $.extend( {}, defaults, options );
47 | this._defaults = defaults;
48 | this.init();
49 | }
50 |
51 | Sticky.prototype = {
52 | construtor: 'Sticky',
53 | scrollTimer: 0, // 滑动计时器
54 | scrollDelay: 150, // 滑动阀值
55 | init: function () {
56 | var me = this;
57 | var initializedIndex = $('[data-' + scope + ']').length;
58 | me.$element = $(me.element);
59 | me.fixedInFun = me.settings.fixedIn;
60 | me.fixedOutFun = me.settings.fixedOut;
61 |
62 | me.$element.each(function (index) {
63 | var fixedItem = {
64 | id: scope + (index + initializedIndex), // 用于定位 sticky 元素
65 | self: this,
66 | $self: $(this),
67 | sticky: false // 标记是否处在 sticky 状态,用于之后的判断
68 | };
69 |
70 | // 确定 sticky 激活位置,当有 NUM_TRIGGER_POSITION 值时以该值为准,没有则以元素当前位置为准
71 | if (me.settings.NUM_TRIGGER_POSITION && SQ.isNumber(me.settings.NUM_TRIGGER_POSITION)) {
72 | fixedItem.triggerPosTop = me.settings.NUM_TRIGGER_POSITION;
73 | } else {
74 | // 设置占位 DOM
75 | if (me.settings.PLACEHOLD) {
76 | me._setPlaceholder(fixedItem);
77 | }
78 | // 获取元素位置 top 值
79 | if (fixedItem.self.getBoundingClientRect()) {
80 | fixedItem.triggerPosTop = fixedItem.self.getBoundingClientRect().top + window.scrollY;
81 | } else {
82 | console.warn('Not Support getBoundingClientRect');
83 | }
84 | // 当元素处于页面顶端则立即设置为 sticky 布局
85 | // UC 浏览器在实际渲染时会有问题,不建议用 sticky.js 来实现顶部导航的固定布局(直接使用 CSS)
86 | if (fixedItem.self.triggerPosTop === 0) {
87 | me._setFixed(fixedItem);
88 | }
89 | }
90 | // 触发绑定
91 | me._trigger(fixedItem);
92 | });
93 | },
94 | /**
95 | * 设置 sticky 元素占位 DOM
96 | * @param fixedItem
97 | * @private
98 | */
99 | _setPlaceholder: function (fixedItem) {
100 | var $dom = fixedItem.$self;
101 | var height = $dom.height() + parseInt($dom.css('padding-top'), 10) + parseInt($dom.css('padding-bottom'), 10) + parseInt($dom.css('margin-top'), 10) + parseInt($dom.css('margin-bottom'), 10);
102 | var $placeholderDom = $('').css({
103 | display: 'none',
104 | width: $dom.width(),
105 | height: height,
106 | background: $dom.css('background')
107 | });
108 | $placeholderDom.insertAfter(fixedItem.$self);
109 | },
110 | /**
111 | * 设置触发事件及触发条件
112 | * @param fixedItem
113 | * @private
114 | */
115 | _trigger: function (fixedItem) {
116 | var me = this;
117 | window.requestAnimationFrame = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame;
118 | // 高级浏览器使用 requestAnimationFrame
119 | function advancedWatchEvent() {
120 | var scrollTop = window.scrollY;
121 | if (scrollTop >= fixedItem.triggerPosTop && !fixedItem.sticky) {
122 | me._setFixed(fixedItem);
123 | } else if (scrollTop < fixedItem.triggerPosTop && fixedItem.sticky) {
124 | me._removeFixed(fixedItem);
125 | }
126 | window.requestAnimationFrame(advancedWatchEvent);
127 | }
128 | // 不支持 requestAnimationFrame 的浏览器使用常用事件
129 | function normalWatchEvent() {
130 | var mobile = 'android-ios';
131 | // 触发函数
132 | function fire() {
133 | var scrollTop = window.scrollY;
134 | if (scrollTop >= fixedItem.triggerPosTop && !fixedItem.$self.hasClass('sq-sticky')) {
135 | me._setFixed(fixedItem);
136 | } else if (scrollTop < fixedItem.triggerPosTop && fixedItem.$self.hasClass('sq-sticky')) {
137 | me._removeFixed(fixedItem);
138 | }
139 | }
140 | // 触摸设备使用 touchstart 事件
141 | if (mobile.indexOf(SQ.ua.os.name) !== -1) {
142 | $(window).on('touchstart', function () {
143 | // 在触摸滑动时浏览器会锁死进程,滑动停止后才会触发 touchstart 事件,而此时 scrollTop 值
144 | // 为触摸时的数值,所以添加 setTimeout 来计算获取滑动停止后的数值。
145 | setTimeout(function () {
146 | fire();
147 | }, 150);
148 | });
149 | } else {
150 | $(window).on('scroll', function () {
151 | // 添加 scroll 事件相应伐值,优化其性能
152 | if (!me.scrollTimer) {
153 | me.scrollTimer = setTimeout(function () {
154 | fire();
155 | me.scrollTimer = 0;
156 | }, me.scrollDelay);
157 | }
158 | });
159 | }
160 | }
161 |
162 | if (window.requestAnimationFrame) {
163 | window.requestAnimationFrame(advancedWatchEvent);
164 | } else {
165 | normalWatchEvent();
166 | }
167 | },
168 | _setFixed: function (fixedItem) {
169 | var me = this;
170 | var posCss = me.settings.ARRY_FIXED_POSITION;
171 | var $placeholderDom = $('#' + fixedItem.id);
172 |
173 | fixedItem.$self.css({
174 | 'position': 'fixed',
175 | 'top': posCss[0],
176 | 'right': posCss[1],
177 | 'bottom': posCss[2],
178 | 'left': posCss[3],
179 | 'z-index': me.settings.NUM_ZINDEX
180 | });
181 | fixedItem.sticky = true;
182 |
183 | if (me.settings.PLACEHOLD && $placeholderDom.length) {
184 | $placeholderDom.show();
185 | }
186 |
187 | if (me.settings.ANIMATE) {
188 | var animateClassName = me.settings.ANIMATE.indexOf('.') === 0 ? me.settings.ANIMATE.slice(1) : me.settings.ANIMATE;
189 | fixedItem.$self.addClass('animated ' + animateClassName);
190 | }
191 |
192 | if (me.fixedInFun) {
193 | me.fixedInFun();
194 | }
195 | },
196 | _removeFixed: function (fixedItem) {
197 | var me = this;
198 | var $placeholderDom = $('#' + fixedItem.id);
199 |
200 | fixedItem.$self.attr('style', '');
201 | fixedItem.sticky = false;
202 |
203 | if (me.settings.PLACEHOLD && $placeholderDom.length) {
204 | $placeholderDom.hide();
205 | }
206 |
207 | if (me.fixedOutFun) {
208 | me.fixedOutFun();
209 | }
210 | }
211 | };
212 |
213 | $.fn.sticky = function ( options ) {
214 | var isZepto = typeof Zepto !== 'undefined' ? true : false;
215 | var isJQuery = typeof jQuery !== 'undefined' ? true : false;
216 | var plugin;
217 |
218 | options = options || {};
219 | options.selector = this.selector;
220 |
221 | this.each(function() {
222 | if (isJQuery) {
223 | if ( !$.data( this, scope ) ) {
224 | $.data( this, scope, new Sticky( this, options ) );
225 | }
226 | } else if (isZepto) {
227 | if (!$(this).data(scope)) {
228 | plugin = new Sticky( this, options );
229 | $(this).data(scope, 'initialized');
230 | }
231 | }
232 | });
233 | // chain jQuery functions
234 | return this;
235 | };
236 |
237 | })($);
--------------------------------------------------------------------------------
/src/styles/normalize.less:
--------------------------------------------------------------------------------
1 | //
2 | // Normalize
3 | // @desc: 标准化样式
4 | // ---------------------------------------------------------------------------
5 |
6 | // normalize.css
7 | // 以下为 normalize.css 拷贝
8 | // 更新时请使用 bower
9 | // --------------------------------------------
10 |
11 | /* normalize.css v3.0.2 | MIT License | git.io/normalize */
12 |
13 | /**
14 | * 1. Set default font family to sans-serif.
15 | * 2. Prevent iOS text size adjust after orientation change, without disabling
16 | * user zoom.
17 | */
18 |
19 | html {
20 | font-family: sans-serif; /* 1 */
21 | -ms-text-size-adjust: 100%; /* 2 */
22 | -webkit-text-size-adjust: 100%; /* 2 */
23 | }
24 |
25 | /**
26 | * Remove default margin.
27 | */
28 |
29 | body {
30 | margin: 0;
31 | }
32 |
33 | /* HTML5 display definitions
34 | ========================================================================== */
35 |
36 | /**
37 | * Correct `block` display not defined for any HTML5 element in IE 8/9.
38 | * Correct `block` display not defined for `details` or `summary` in IE 10/11
39 | * and Firefox.
40 | * Correct `block` display not defined for `main` in IE 11.
41 | */
42 |
43 | article,
44 | aside,
45 | details,
46 | figcaption,
47 | figure,
48 | footer,
49 | header,
50 | hgroup,
51 | main,
52 | menu,
53 | nav,
54 | section,
55 | summary {
56 | display: block;
57 | }
58 |
59 | /**
60 | * 1. Correct `inline-block` display not defined in IE 8/9.
61 | * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
62 | */
63 |
64 | audio,
65 | canvas,
66 | progress,
67 | video {
68 | display: inline-block; /* 1 */
69 | vertical-align: baseline; /* 2 */
70 | }
71 |
72 | /**
73 | * Prevent modern browsers from displaying `audio` without controls.
74 | * Remove excess height in iOS 5 devices.
75 | */
76 |
77 | audio:not([controls]) {
78 | display: none;
79 | height: 0;
80 | }
81 |
82 | /**
83 | * Address `[hidden]` styling not present in IE 8/9/10.
84 | * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
85 | */
86 |
87 | [hidden],
88 | template {
89 | display: none;
90 | }
91 |
92 | /* Links
93 | ========================================================================== */
94 |
95 | /**
96 | * Remove the gray background color from active links in IE 10.
97 | */
98 |
99 | a {
100 | background-color: transparent;
101 | }
102 |
103 | /**
104 | * Improve readability when focused and also mouse hovered in all browsers.
105 | */
106 |
107 | a:active,
108 | a:hover {
109 | outline: 0;
110 | }
111 |
112 | /* Text-level semantics
113 | ========================================================================== */
114 |
115 | /**
116 | * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
117 | */
118 |
119 | abbr[title] {
120 | border-bottom: 1px dotted;
121 | }
122 |
123 | /**
124 | * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
125 | */
126 |
127 | b,
128 | strong {
129 | font-weight: bold;
130 | }
131 |
132 | /**
133 | * Address styling not present in Safari and Chrome.
134 | */
135 |
136 | dfn {
137 | font-style: italic;
138 | }
139 |
140 | /**
141 | * Address variable `h1` font-size and margin within `section` and `article`
142 | * contexts in Firefox 4+, Safari, and Chrome.
143 | */
144 |
145 | h1 {
146 | font-size: 2em;
147 | margin: 0.67em 0;
148 | }
149 |
150 | /**
151 | * Address styling not present in IE 8/9.
152 | */
153 |
154 | mark {
155 | background: #ff0;
156 | color: #000;
157 | }
158 |
159 | /**
160 | * Address inconsistent and variable font size in all browsers.
161 | */
162 |
163 | small {
164 | font-size: 80%;
165 | }
166 |
167 | /**
168 | * Prevent `sub` and `sup` affecting `line-height` in all browsers.
169 | */
170 |
171 | sub,
172 | sup {
173 | font-size: 75%;
174 | line-height: 0;
175 | position: relative;
176 | vertical-align: baseline;
177 | }
178 |
179 | sup {
180 | top: -0.5em;
181 | }
182 |
183 | sub {
184 | bottom: -0.25em;
185 | }
186 |
187 | /* Embedded content
188 | ========================================================================== */
189 |
190 | /**
191 | * Remove border when inside `a` element in IE 8/9/10.
192 | */
193 |
194 | img {
195 | border: 0;
196 | }
197 |
198 | /**
199 | * Correct overflow not hidden in IE 9/10/11.
200 | */
201 |
202 | svg:not(:root) {
203 | overflow: hidden;
204 | }
205 |
206 | /* Grouping content
207 | ========================================================================== */
208 |
209 | /**
210 | * Address margin not present in IE 8/9 and Safari.
211 | */
212 |
213 | figure {
214 | margin: 1em 40px;
215 | }
216 |
217 | /**
218 | * Address differences between Firefox and other browsers.
219 | */
220 |
221 | hr {
222 | -moz-box-sizing: content-box;
223 | box-sizing: content-box;
224 | height: 0;
225 | }
226 |
227 | /**
228 | * Contain overflow in all browsers.
229 | */
230 |
231 | pre {
232 | overflow: auto;
233 | }
234 |
235 | /**
236 | * Address odd `em`-unit font size rendering in all browsers.
237 | */
238 |
239 | code,
240 | kbd,
241 | pre,
242 | samp {
243 | font-family: monospace, monospace;
244 | font-size: 1em;
245 | }
246 |
247 | /* Forms
248 | ========================================================================== */
249 |
250 | /**
251 | * Known limitation: by default, Chrome and Safari on OS X allow very limited
252 | * styling of `select`, unless a `border` property is set.
253 | */
254 |
255 | /**
256 | * 1. Correct color not being inherited.
257 | * Known issue: affects color of disabled elements.
258 | * 2. Correct font properties not being inherited.
259 | * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
260 | */
261 |
262 | button,
263 | input,
264 | optgroup,
265 | select,
266 | textarea {
267 | color: inherit; /* 1 */
268 | font: inherit; /* 2 */
269 | margin: 0; /* 3 */
270 | }
271 |
272 | /**
273 | * Address `overflow` set to `hidden` in IE 8/9/10/11.
274 | */
275 |
276 | button {
277 | overflow: visible;
278 | }
279 |
280 | /**
281 | * Address inconsistent `text-transform` inheritance for `button` and `select`.
282 | * All other form control elements do not inherit `text-transform` values.
283 | * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
284 | * Correct `select` style inheritance in Firefox.
285 | */
286 |
287 | button,
288 | select {
289 | text-transform: none;
290 | }
291 |
292 | /**
293 | * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
294 | * and `video` controls.
295 | * 2. Correct inability to style clickable `input` types in iOS.
296 | * 3. Improve usability and consistency of cursor style between image-type
297 | * `input` and others.
298 | */
299 |
300 | button,
301 | html input[type="button"], /* 1 */
302 | input[type="reset"],
303 | input[type="submit"] {
304 | -webkit-appearance: button; /* 2 */
305 | cursor: pointer; /* 3 */
306 | }
307 |
308 | /**
309 | * Re-set default cursor for disabled elements.
310 | */
311 |
312 | button[disabled],
313 | html input[disabled] {
314 | cursor: default;
315 | }
316 |
317 | /**
318 | * Remove inner padding and border in Firefox 4+.
319 | */
320 |
321 | button::-moz-focus-inner,
322 | input::-moz-focus-inner {
323 | border: 0;
324 | padding: 0;
325 | }
326 |
327 | /**
328 | * Address Firefox 4+ setting `line-height` on `input` using `!important` in
329 | * the UA stylesheet.
330 | */
331 |
332 | input {
333 | line-height: normal;
334 | }
335 |
336 | /**
337 | * It's recommended that you don't attempt to style these elements.
338 | * Firefox's implementation doesn't respect box-sizing, padding, or width.
339 | *
340 | * 1. Address box sizing set to `content-box` in IE 8/9/10.
341 | * 2. Remove excess padding in IE 8/9/10.
342 | */
343 |
344 | input[type="checkbox"],
345 | input[type="radio"] {
346 | box-sizing: border-box; /* 1 */
347 | padding: 0; /* 2 */
348 | }
349 |
350 | /**
351 | * Fix the cursor style for Chrome's increment/decrement buttons. For certain
352 | * `font-size` values of the `input`, it causes the cursor style of the
353 | * decrement button to change from `default` to `text`.
354 | */
355 |
356 | input[type="number"]::-webkit-inner-spin-button,
357 | input[type="number"]::-webkit-outer-spin-button {
358 | height: auto;
359 | }
360 |
361 | /**
362 | * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
363 | * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
364 | * (include `-moz` to future-proof).
365 | */
366 |
367 | input[type="search"] {
368 | -webkit-appearance: textfield; /* 1 */
369 | -moz-box-sizing: content-box;
370 | -webkit-box-sizing: content-box; /* 2 */
371 | box-sizing: content-box;
372 | }
373 |
374 | /**
375 | * Remove inner padding and search cancel button in Safari and Chrome on OS X.
376 | * Safari (but not Chrome) clips the cancel button when the search input has
377 | * padding (and `textfield` appearance).
378 | */
379 |
380 | input[type="search"]::-webkit-search-cancel-button,
381 | input[type="search"]::-webkit-search-decoration {
382 | -webkit-appearance: none;
383 | }
384 |
385 | /**
386 | * Define consistent border, margin, and padding.
387 | */
388 |
389 | fieldset {
390 | border: 1px solid #c0c0c0;
391 | margin: 0 2px;
392 | padding: 0.35em 0.625em 0.75em;
393 | }
394 |
395 | /**
396 | * 1. Correct `color` not being inherited in IE 8/9/10/11.
397 | * 2. Remove padding so people aren't caught out if they zero out fieldsets.
398 | */
399 |
400 | legend {
401 | border: 0; /* 1 */
402 | padding: 0; /* 2 */
403 | }
404 |
405 | /**
406 | * Remove default vertical scrollbar in IE 8/9/10/11.
407 | */
408 |
409 | textarea {
410 | overflow: auto;
411 | }
412 |
413 | /**
414 | * Don't inherit the `font-weight` (applied by a rule above).
415 | * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
416 | */
417 |
418 | optgroup {
419 | font-weight: bold;
420 | }
421 |
422 | /* Tables
423 | ========================================================================== */
424 |
425 | /**
426 | * Remove most spacing between table cells.
427 | */
428 |
429 | table {
430 | border-collapse: collapse;
431 | border-spacing: 0;
432 | }
433 |
434 | td,
435 | th {
436 | padding: 0;
437 | }
438 |
--------------------------------------------------------------------------------
/doc_tpl/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Squirrel 前端开发框架
12 |
13 |
14 |
18 |
19 |
34 |
35 |
36 |
37 |
Squirrel 3Beta
38 |
移动前端开发框架,提供简单、快速的 Web 开发体验。
39 |
40 | - 最新版本:%VERSION%
41 | - 更新时间:%UPDATE%
42 | - 更新说明
43 |
44 |
45 |
46 |
47 |
48 |
49 |
53 |
54 |
55 | -
56 |
57 |
58 |
组件丰富
59 |
Squirrel 框架提供丰富的样式及交互组件,能帮助您轻松构建 Web 应用。
60 |
61 |
62 | -
63 |
64 |
65 |
多系统支持
66 |
默认支持 Android、iOS 操作系统,也可方便的扩展支持 WP8、Firefox OS 等系统。
67 |
68 |
69 | -
70 |
71 |
72 |
响应式设计
73 |
能适应各种屏幕尺寸、分辨率的设备,并针对触摸操作体验进行了优化。
74 |
75 |
76 | -
77 |
78 |
79 |
扩展简便
80 |
采用 LESS 技术、CMD 规范、jQuery 插件开发模式,无论扩展还是移植都方便。
81 |
82 |
83 | -
84 |
85 |
86 |
定制灵活
87 |
提供定制工具,可以根据实际需要灵活的定制框架。
88 |
89 |
90 | -
91 |
92 |
93 |
开源协议
94 |
Squirrel 框架遵循 MIT 协议,无论公司还是个人,都可以免费、自由使用。
95 |
96 |
97 |
98 |
99 |
100 |
101 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |

124 |
使用手机浏览体验
125 |
126 |
127 |
128 |
129 |
130 | 合作伙伴
131 | Squirrel 为以团队下提供了技术支持。
132 |
133 | 
134 | 
135 |
136 |
137 |
138 |
139 |
143 |
144 |
145 | -
146 |
iinterest
147 |
148 | Developer
149 |
150 |
151 |
152 |
153 |
154 |
155 | -
156 |
薛小小
157 |
158 | Designer
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 | -
169 |
插件
170 | 欢迎您为 Squirrel 贡献高实用性的功能插件,以方便其他使用者。开始之前请先阅读插件开发流程。
171 |
172 | -
173 |
主题
174 | 如果您制作了一款基于 Squirrel 的主题并且符合主题的开发规范,我们非常欢迎您将它提交至主题库。
175 |
176 | -
177 |
模式库
178 | 模式库(DPL)组件与业务结合紧密,具备高质量且完整的交互体验,如果您为您的项目开发了类似的组件,欢迎提交至设计模式库。
179 |
180 | -
181 |
用户体验
182 | 希望您能将任何体验相关的意见或建议,甚至新的功能需求提交至 github issues「体验」。
183 |
184 | -
185 |
缺陷
186 | 如果使用中发现任何缺陷(BUG),请提交至 github issues「BUG」,同时也请报告您的使用版本、环境,我们会尽快确认、修复。
187 |
188 |
189 |
190 |
191 |
207 |
208 |
209 |
210 |
--------------------------------------------------------------------------------
/src/scripts/suggest.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @file SQ.Suggest 联想词插件
3 | * @version 1.0.0
4 | */
5 |
6 | /**
7 | * @changelog
8 | * 1.0.0 * 重写插件,调用方式改为 $. 链式调用。
9 | * 0.5.10 * 修复 jshint 问题
10 | * 0.5.9 * 修复在输入搜索后删除搜索词,再次输入相同字符,首字符无请求问题。issues#11
11 | * 0.5.8 * 修复 IE 下对 XHR 对象处理问题。
12 | * 0.5.7 * 修复多次发送请求时,老请求因为响应慢覆盖新请求问题。
13 | * 0.5.6 * 修改插件名称为 Suggest。
14 | * 0.5.5 * 完成搜索联想词基本功能。
15 | * 0.0.1 + 新建。
16 | */
17 |
18 | /*global $, SQ, console, jQuery */
19 | (function ($) {
20 | 'use strict';
21 | /**
22 | * @name Suggest
23 | * @classdesc 搜索联想词插件
24 | * @constructor
25 | * @param {object} config 插件配置(下面的参数为配置项,配置会写入属性)
26 | * @param {string} config.API 联想词接口
27 | * @param {string} config.CSS_CLEAR_BTN 设置清空按钮样式名称,默认为 .sq-suggest-clear-btn
28 | * @param {string} config.CSS_SUGGEST_PANEL 设置联想词展示面板样式名称,默认为 .sq-suggest-result
29 | * @param {number} config.NUM_TIMER_DELAY 监测输入框间隔时长,默认为 300ms
30 | * @param {number} config.NUM_XHR_TIMEER XHR 超时时长,默认为 5000ms
31 | * @param {number} config.NUM_SUCCESS_CODE XHR 成功状态码,默认为 200
32 | * @param {function} config.beforeStart 检测输入框前的回调函数
33 | * @param {function} config.start 开始检测输入框时回调函数
34 | * @param {function} config.show(data) 显示联想词面板时回调函数,data 为 XHR 返回数据
35 | * @param {function} config.clear 清除时回调函数
36 | * @example $('.J_suggest').suggest({
37 | API: 'data/suggest.json',
38 | CSS_CLEAR_BTN: '.clear',
39 | CSS_SUGGEST_RESULT: '.suggest-panel',
40 | show: function (data) {
41 | var me = this;
42 | console.log('suggestList: ' + data);
43 | }
44 | });
45 | */
46 | var scope = 'sq-suggest';
47 | var defaults = {
48 | NUM_TIMER_DELAY : 300,
49 | NUM_XHR_TIMEER : 5000,
50 | NUM_SUCCESS_CODE : 200,
51 | suggestion : true
52 | };
53 |
54 | function Suggest ( element, options ) {
55 | this.element = element;
56 | this.settings = $.extend( {}, defaults, options );
57 | this._defaults = defaults;
58 | this.init();
59 | }
60 |
61 | Suggest.prototype = {
62 | construtor: 'Suggest',
63 | lastKeyword: '', // 为 300ms(检测时长) 前的关键词
64 | lastSendKeyword : '', // 上一次符合搜索条件的关键词
65 | canSendRequest : true, // 是否可以进行下次联想请求
66 | init: function () {
67 | var me = this;
68 | var clearBtnClassName = '';
69 | var suggestResultClassName = '';
70 |
71 | if (me.settings.CSS_CLEAR_BTN) {
72 | clearBtnClassName = me.settings.CSS_CLEAR_BTN.indexOf('.') !== -1 ? me.settings.CSS_CLEAR_BTN.slice(1) : me.settings.CSS_CLEAR_BTN;
73 | }
74 | if (me.settings.CSS_SUGGEST_RESULT) {
75 | suggestResultClassName = me.settings.CSS_SUGGEST_RESULT.indexOf('.') !== -1 ? me.settings.CSS_SUGGEST_RESULT.slice(1) : me.settings.CSS_SUGGEST_RESULT;
76 | }
77 |
78 | me.$element = $(me.element);
79 | me.$input = me.$element.find('input[type=text]');
80 | me.$clearBtn = $('').addClass(clearBtnClassName);
81 | me.$suggestPanel = $('').addClass(suggestResultClassName);
82 |
83 | me.$input.after(me.$clearBtn);
84 | me.$element.append(me.$suggestPanel);
85 |
86 | me.beforeStartFun = me.settings.beforeStart;
87 | me.startFun = me.settings.start;
88 | me.clearFun = me.settings.clear;
89 | me.showFun = me.settings.show;
90 |
91 | if (me._verify()) {
92 | me._bind();
93 | }
94 | },
95 | _verify : function () {
96 | return true;
97 | },
98 | _bind : function (e) {
99 | var me = this;
100 | me.$input.on('focus', function () {
101 | me.start();
102 | });
103 | me.$input.on('blur', function () {
104 | me.stop();
105 | });
106 | me.$clearBtn.on('click', function () {
107 | me.clear();
108 | });
109 | if (me.beforeStartFun) {
110 | me.beforeStartFun();
111 | }
112 | },
113 | /** 过滤输入内容 */
114 | _filter : function (originalKeyword) {
115 | return originalKeyword.replace(/\s+/g, '').replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '');
116 | },
117 | /** 初始化提示层容器 */
118 | _initSuggest : function () {
119 | var me = this;
120 | me.$suggestPanel.empty();
121 | },
122 | /** 请求数据 */
123 | _requestData : function (keyword) {
124 | var me = this;
125 | var api = me.settings.API;
126 |
127 | //console.log('request -> ' + 'keyword: ' + keyword, 'lastSendKeyword: ' + me.lastSendKeyword);
128 | if (me.xhr) {
129 | me.xhr.abort();
130 | }
131 | me.xhr = $.ajax({
132 | type : 'POST',
133 | url : api,
134 | dataType : 'json',
135 | data : {'keyword': keyword},
136 | timeout : me.settings.NUM_XHR_TIMEER,
137 | success : function (data) {
138 | me.showSuggest(data);
139 | me.lastSendKeyword = keyword;
140 | },
141 | error : function () {
142 | me.canSendRequest = false;
143 | setTimeout(function () {
144 | me.canSendRequest = true;
145 | }, 500);
146 | }
147 | });
148 | },
149 | _compare : function (keyword) {
150 | var me = this;
151 | var cLen = keyword.length;
152 | var lsLen = me.lastSendKeyword.length;
153 | //console.log('keyword: ' + keyword, 'lastSendKeyword: ' + me.lastSendKeyword);
154 |
155 | if (me.lastKeyword === keyword) {
156 | //console.log('same ' + 'me.lastKeyword = ' + me.lastKeyword + ' | ' + 'keyword = ' + keyword + ' | ' + 'me.lastSendKeyword =' + me.lastSendKeyword);
157 | return false;
158 | }
159 |
160 | if (lsLen > 0 && cLen < lsLen) {
161 | me.canSendRequest = true;
162 | }
163 |
164 | if (!me.canSendRequest) {
165 | // canSendRequest 为能否发送请求的判断条件
166 | // 有几种情况会改变 canSendRequest 的值:
167 | // true 情况
168 | // 1、当前输入关键词少于上次发送请求关键词时,canSendRequest 为 true
169 | // 2、请求服务器成功返回并有联想结果时,canSendRequest 为 true
170 | // 3、调用 clear() 函数时,canSendRequest 为 true
171 | // 4、请求服务器失败,500ms 后 canSendRequest 为 true
172 | // false 情况
173 | // 1、请求服务器成功,但返回的 code 与 NUM_SUCCESS_CODE 不一致,canSendRequest 为 false
174 | // 2、请求服务器失败,canSendRequest 为 false
175 | //console.log('!canSendRequest');
176 | return false;
177 | }
178 | if (me.lastSendKeyword === keyword) {
179 | //console.log('关键词相同')
180 | return false;
181 | }
182 | return true;
183 | },
184 | /** 启动计时器,开始监听用户输入 */
185 | start : function () {
186 | var me = this;
187 | me.inputListener = setInterval(function () {
188 | var originalKeyword = me.$input.val();
189 | var keyword = me._filter(originalKeyword);
190 |
191 | if (keyword.length > 0) {
192 | if (me.$clearBtn.css('display') === 'none') {
193 | me.$clearBtn.show();
194 | }
195 | if (me._compare(keyword)) {
196 | me._requestData(keyword);
197 | if (me.startFun) {
198 | me.startFun();
199 | }
200 | }
201 | me.lastKeyword = keyword;
202 | } else {
203 | me.lastKeyword = undefined;
204 | me.clear();
205 | }
206 | }, me.settings.NUM_TIMER_DELAY);
207 | },
208 | /** 停止计时器 */
209 | stop : function () {
210 | var me = this;
211 | clearInterval(me.inputListener);
212 | },
213 | /** 显示提示层 */
214 | showSuggest : function (data) {
215 | var me = this;
216 | var ds = typeof data === 'object' ? data : JSON.parse(data);
217 | if (ds.code !== me.settings.NUM_SUCCESS_CODE) {
218 | me.canSendRequest = false;
219 | return;
220 | }
221 | me.canSendRequest = true;
222 | me._initSuggest();
223 | if (me.showFun) {
224 | me.showFun(ds);
225 | }
226 | },
227 | /** 隐藏提示层 */
228 | hideSuggest : function () {
229 | var me = this;
230 | me.$suggestPanel.hide();
231 | },
232 | /** 清除输入内容 */
233 | clear : function () {
234 | var me = this;
235 | me.$input.val('');
236 | me.hideSuggest();
237 | me.$clearBtn.hide();
238 | me.canSendRequest = true;
239 | me.lastSendKeyword = '';
240 | if (me.clearFun) {
241 | me.clearFun();
242 | }
243 | }
244 | };
245 |
246 | $.fn.suggest = function ( options ) {
247 | var isZepto = typeof Zepto !== 'undefined' ? true : false;
248 | var isJQuery = typeof jQuery !== 'undefined' ? true : false;
249 | var plugin;
250 |
251 | options = options || {};
252 | options.selector = this.selector;
253 |
254 | this.each(function() {
255 | if (isJQuery) {
256 | if ( !$.data( this, scope ) ) {
257 | $.data( this, scope, new Suggest( this, options ) );
258 | }
259 | } else if (isZepto) {
260 | if (!$(this).data(scope)) {
261 | plugin = new Suggest( this, options );
262 | $(this).data(scope, 'initialized');
263 | }
264 | }
265 | });
266 | // chain jQuery functions
267 | return this;
268 | };
269 |
270 | })($);
--------------------------------------------------------------------------------