├── README.md ├── 3.瀑布流 ├── img │ ├── 1.jpg │ ├── 10.jpg │ ├── 11.jpg │ ├── 12.jpg │ ├── 13.jpg │ ├── 14.jpg │ ├── 15.jpg │ ├── 16.jpg │ ├── 17.jpg │ ├── 18.jpg │ ├── 19.jpg │ ├── 2.jpg │ ├── 20.jpg │ ├── 21.jpg │ ├── 22.jpg │ ├── 23.jpg │ ├── 24.jpg │ ├── 25.jpg │ ├── 26.jpg │ ├── 27.jpg │ ├── 28.jpg │ ├── 29.jpg │ ├── 3.jpg │ ├── 30.jpg │ ├── 31.jpg │ ├── 32.jpg │ ├── 33.jpg │ ├── 34.jpg │ ├── 35.jpg │ ├── 36.jpg │ ├── 37.jpg │ ├── 38.jpg │ ├── 4.jpg │ ├── 5.jpg │ ├── 6.jpg │ ├── 7.jpg │ ├── 8.jpg │ └── 9.jpg ├── example_01.html ├── example_02.html └── README.md ├── 8.拖拽效果 ├── img │ └── screenshoot.png ├── example_01.html └── reademe.md ├── 6.调整窗口大小效果 ├── img │ └── screenshot.png ├── example_01.html └── README.md ├── 9.跟随页面滚动效果 ├── readme.md └── example_01.html ├── 1.滚动效果 ├── example_03.html ├── example_01.html ├── example_02.html ├── example_04.html └── readme.md ├── 5.Tab选项卡 ├── example_01.html ├── example_03.html ├── example_02.html └── README.md ├── 7.手风琴效果 ├── example_01.html └── reademe.md ├── 4.弹出框 ├── example_01.html └── READEME.md ├── 10.鼠标右键菜单效果 ├── example_01.html └── readme.md └── 2.日历选择器 ├── example_01.html └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # web-advanced-practice 2 | 3 | ⚠️ 常见 Web 特效实战,一共十期,帮助你提高 HTML、JS 基础。 4 | -------------------------------------------------------------------------------- /3.瀑布流/img/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/1.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/10.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/10.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/11.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/11.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/12.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/12.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/13.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/13.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/14.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/14.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/15.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/15.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/16.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/16.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/17.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/17.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/18.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/18.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/19.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/19.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/2.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/20.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/20.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/21.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/21.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/22.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/22.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/23.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/23.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/24.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/24.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/25.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/25.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/26.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/26.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/27.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/27.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/28.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/28.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/29.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/29.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/3.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/30.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/30.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/31.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/31.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/32.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/32.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/33.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/33.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/34.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/34.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/35.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/35.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/36.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/36.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/37.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/37.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/38.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/38.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/4.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/5.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/6.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/7.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/7.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/8.jpg -------------------------------------------------------------------------------- /3.瀑布流/img/9.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/3.瀑布流/img/9.jpg -------------------------------------------------------------------------------- /8.拖拽效果/img/screenshoot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/8.拖拽效果/img/screenshoot.png -------------------------------------------------------------------------------- /6.调整窗口大小效果/img/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/MiYogurt/web-advanced-practice/HEAD/6.调整窗口大小效果/img/screenshot.png -------------------------------------------------------------------------------- /9.跟随页面滚动效果/readme.md: -------------------------------------------------------------------------------- 1 | 跟随页面滚动非常的简单,就是设置元素的`top`值即可,代码量只有20行。 2 | 3 | 4 | 1.准备 HTML 5 | 6 | ```html 7 |
8 | 9 |
10 | ``` 11 | 12 | 13 | 2.准备css 14 | 15 | 一定要记得把动画个加上 16 | 17 | ```css 18 | body{ 19 | min-height: 4000px; 20 | } 21 | .roll{ 22 | position: relative; 23 | width: 400px; 24 | height: 200px; 25 | background: red; 26 | 27 | transition: all .5s; 28 | } 29 | ``` 30 | 31 | 32 | 3.js逻辑 33 | 34 | 这个 `20` 最初始 `roll`距离顶部的距离,具体看你的设置多少。 35 | 36 | ```js 37 | const roll = document.querySelector('.roll'); 38 | window.onscroll = (e) => { 39 | if(document.body.scrollTop > 20 ){ 40 | roll.style.top = (document.body.scrollTop + 20) + 'px'; 41 | } 42 | } 43 | ``` -------------------------------------------------------------------------------- /9.跟随页面滚动效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Index 8 | 21 | 22 | 23 |
24 | 25 |
26 | 27 | 35 | 36 | -------------------------------------------------------------------------------- /1.滚动效果/example_03.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 回到顶部 8 | 9 | 14 | 15 | 16 |
17 | 回到顶部 18 | 19 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /3.瀑布流/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 瀑布流 8 | 33 | 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 |
60 |
61 | 62 |
63 |
64 | 65 |
66 |
67 | 68 | -------------------------------------------------------------------------------- /1.滚动效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 页面滚动效果 9 | 10 | 31 | 32 | 33 | 34 |
35 |

诗词推荐

36 |
37 | 44 |
45 |
46 | 47 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /1.滚动效果/example_02.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 页面滚动效果 9 | 10 | 31 | 32 | 33 | 34 |
35 |

诗词推荐

36 |
37 | 44 |
45 |
46 | 47 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /5.Tab选项卡/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Tab 8 | 63 | 64 | 65 |
66 | 1 67 |
68 |

Hello Tab

69 | baidu.com 70 |
71 | 2 72 |
222
73 | 3 74 |
333
75 | 4 76 |
4444
77 |
78 | 79 | -------------------------------------------------------------------------------- /8.拖拽效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 拖拽 8 | 9 | 32 | 33 | 34 |
35 |
36 | Title 37 |
38 |
39 | 40 |
41 |
42 | 43 | 89 | 90 | -------------------------------------------------------------------------------- /1.滚动效果/example_04.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 轮播 8 | 43 | 44 | 45 | 52 | 53 | 54 | 85 | 86 | -------------------------------------------------------------------------------- /7.手风琴效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 手风琴效果 8 | 37 | 38 | 39 |
40 |
41 |
满江红·点火樱桃
42 |
点火樱桃,照一架、荼蘼如雪。春正好,见龙孙穿破,紫苔苍壁。乳燕引雏飞力弱,流莺唤友娇声怯。问春归、不肯带愁归,肠千结。 43 | 层楼望,春山叠;家何在?烟波隔。把古今遗恨,向他谁说?蝴蝶不传千里梦,子规叫断三更月。听声声、枕上劝人归,归难得。
44 |
菩萨蛮·送曹君之庄所
45 |
人间岁月堂堂去,劝君快上青云路。圣处一灯传,工夫萤雪边。 46 | 麴生风味恶,辜负西窗约。沙岸片帆开,寄书无雁来。
47 |
丑奴儿·晚来一阵风兼雨
48 |
晚来一阵风兼雨,洗尽炎光。理罢笙簧,却对菱花淡淡妆。 49 | 绛绡缕薄冰肌莹,雪腻酥香。笑语檀郎:今夜纱厨枕簟凉。
50 |
清商怨·葭萌驿作
51 |
江头日暮痛饮。乍雪晴犹凛。山驿凄凉,灯昏人独寝。 52 | 鸳机新寄断锦。叹往事、不堪重省。梦破南楼,绿云堆一枕。
53 |
54 |
55 | 56 | 91 | 92 | -------------------------------------------------------------------------------- /8.拖拽效果/reademe.md: -------------------------------------------------------------------------------- 1 | 拖拽效果,和调整窗口大小其实差不多,首先我们来看一张图。 2 | 3 | ![](./img/screenshoot.png) 4 | 5 | 黑色的小圆点,是我们鼠标单击的点。 6 | 7 | 红线的距离,可以通过`e.pageX`获得。 8 | 9 | 蓝线的距离,可以通过可以移动的这个`Div`盒子的 `offsetLeft`拿到。 10 | 11 | 此时我们就可以拿到,黄线的长度,当鼠标,也就是小圆点往右移动的时候,黄线是不会变的,而红线会变长,我们再次通过`e.pageX`拿到这个红线的长,我们通过红线的长度,减去不变的黄线的长度,得到的就是新的蓝线长度,也就是元素需要设置的`left`的值。 12 | 13 | 1.首先准备HTML结构 14 | 15 | ```html 16 |
17 |
18 | Title 19 |
20 |
21 | 22 |
23 |
24 | ``` 25 | 26 | 2.添加一些样式 27 | 28 | ```css 29 | *{ 30 | margin: 0; 31 | padding: 0; 32 | } 33 | 34 | .dialog{ 35 | width: 480px; 36 | height: 300px; 37 | background: #eee; 38 | position: absolute; 39 | } 40 | 41 | .dialog-title{ 42 | width: 480px; 43 | height: 40px; 44 | background: #999; 45 | cursor: move; 46 | text-align: center; 47 | line-height: 40px; 48 | } 49 | ``` 50 | 51 | 3. 加上我们的 JavaScript 逻辑 52 | 53 | ```js 54 | const dragEle = document.querySelector('#canDrag'); 55 | const canMove = document.querySelector('#canMove'); 56 | 57 | dragEle.onselectstart = canMove.onselectstart = () => { 58 | return false; 59 | } // 禁止被选中 60 | 61 | let timer; 62 | 63 | const mouse = { 64 | x: 0, 65 | y: 0 66 | } // 记录当前鼠标移动的坐标点 67 | 68 | const distance = { 69 | topTop: 0, 70 | topLeft: 0 71 | } // 鼠标按下时候距离 CanMove 左上角的距离 72 | 73 | document.onmousemove = (e) => { 74 | mouse.x = e.pageX; 75 | mouse.y = e.pageY; 76 | } // 记录鼠标坐标 77 | 78 | document.onmouseup = document.ondrag = (e) => { 79 | clearInterval(timer); 80 | timer = null; 81 | } // 鼠标弹起,清空设置left的定时器 82 | 83 | dragEle.onmousedown = (e) => { 84 | distance.topLeft = e.pageX - canMove.offsetLeft; 85 | distance.topTop = e.pageY - canMove.offsetTop; 86 | 87 | timer = setInterval(setPosition, 10); 88 | } 89 | 90 | function setPosition(){ 91 | const maxX = (document.body.clientWidth || document.documentElement.clientWidth) - canMove.offsetWidth; 92 | const maxY = (document.body.clientHeight || document.documentElement.clientHeight) - canMove.offsetHeight; 93 | 94 | canMove.style.left = Math.max(Math.min((mouse.x - distance.topLeft), maxX), 0) + 'px'; 95 | canMove.style.top = Math.max(Math.min((mouse.y - distance.topTop), maxY), 0) + 'px'; 96 | } 97 | ``` 98 | 99 | `maxX` 就是我们最多可以移动长度,它等于浏览器可视区的宽度,减去盒子的宽度。 100 | 101 | `Math.min((mouse.x - distance.topLeft), maxX) ` 是为了让`left`不能超出可视区域右边 102 | 103 | `Math.max` 是为了让 `left` 不能为负数,也就是不能超出可视区的左边。 104 | -------------------------------------------------------------------------------- /5.Tab选项卡/example_03.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS Tab 8 | 9 | 59 | 60 | 61 |
62 | 68 |
69 |
70 |

山有木兮木有枝,心悦君兮君不知。

71 |
72 |
2
73 |
3
74 |
4
75 |
76 |
77 | 78 | 79 | 107 | 108 | -------------------------------------------------------------------------------- /7.手风琴效果/reademe.md: -------------------------------------------------------------------------------- 1 | 手风琴效果其实就是通过JS改变元素的`height`,然后加上`transition`来让css动起来。 2 | 3 | ## 准备HTML结构 4 | 这里我们使用 `dl` , 因为 `dt` 刚好可以模拟标题, `dd` 模拟内容。 5 | 6 | ```html 7 |
8 |
9 |
One Item
10 |
One Item Content .
11 |
two Item
12 |
two Item Content .
13 |
3 Item
14 |
3 Item Content .
15 |
4 Item
16 |
4 Item Content .
17 |
18 |
19 | ``` 20 | 21 | ## 准备css样式 22 | 23 | ```css 24 | *{margin: 0;padding: 0;} 25 | .panel{ 26 | width: 480px; 27 | min-height: 160px; 28 | margin: 50px auto; 29 | background: #eee; 30 | } 31 | .panel dt{ 32 | min-height: 40px; 33 | line-height: 40px; 34 | background: #f9f9f9; 35 | padding-left: 10px; 36 | cursor: pointer; 37 | } 38 | 39 | .panel dd{ 40 | padding-left: 10px; 41 | height: 0; 42 | overflow: hidden; 43 | transition: height .5s; 44 | } 45 | ``` 46 | 47 | ## 接下来书写我们的JS逻辑 48 | 49 | 首先我们为我们每一个 dt 绑定 `click` 事件 50 | 51 | 同样,我们使用 `Array.from` 将 `NodeList`转换为数组,通常能尽量不用`for`的我就不会用`for`。 52 | 53 | `nextElementSibling` 代表下一个相邻的元素。 54 | 55 | ```js 56 | const dtDoms = document.querySelectorAll('dt'); 57 | 58 | Array.from(dtDoms).forEach((dtDom) => { 59 | dtDom.onclick = (e) => { 60 | const dd = e.target.nextElementSibling; // 被点击的dt的下一个dd元素 61 | toggle(dd); 62 | } 63 | }); 64 | 65 | ``` 66 | 67 | 完成 `toggle` 函数 68 | 69 | ```js 70 | function toggle(target){ 71 | const ddDoms = document.querySelectorAll('dd'); 72 | if(target.style.height == '' || target.style.height == '0px' ) { // 第一次的时候 height 为 ‘’ 空字符串. 73 | for(dd of ddDoms){ 74 | dd.style.height = '0px'; 75 | } 76 | Object.assign(target.style, { 77 | position: "absolute", 78 | left: "-2000px", 79 | top: "-2000px", 80 | height: "auto", 81 | width: "480px" 82 | }); 83 | const height = target.offsetHeight; 84 | target.style.cssText = 'height: 0px'; 85 | requestAnimationFrame(() => { 86 | target.style.cssText = 'height: ' + height + 'px'; 87 | }) 88 | }else{ 89 | target.style.height = '0px'; 90 | } 91 | } 92 | ``` 93 | 94 | 首先为我们要拿到所有的 dd 方便之后重置样式。 然后我们判断目标元素`target`上面的`height`, 初始的时候是 `''`空字符串,之后就是`0px`了,当是`0px`的时候,就说明是关闭状态。 95 | 96 | 之后我们通过`for of`遍历所有 dd, 先清空一下未关闭的标签。 97 | 98 | 然后我们可以通过 Object.assign 来把后面的对象上面的属性拷贝到 target.style 上面去。 99 | 100 | 通常我们是无法获取一个隐藏元素的高的,所以我们首先把当前的 dd, 用绝对定位定位到屏幕外面去,之后我们就可以通过`offsetHeight`获取高度了。 101 | 102 | 有的人会问我为什么不直接用`auto`, 因为`auto`是不会产生动画的。 当然你也可以设置一个固定的高度。 103 | 104 | 之后我们,先把高度重置为0,之后在浏览器重新绘制下一帧的时候再设置高度。 105 | 106 | 你可以尝试一下去掉`requestAnimationFrame`,你同样会发现动画失效了。 107 | 108 | 我猜测可能是浏览器做了处理,短时间的切换属性`height`会被忽略掉。 109 | 110 | -------------------------------------------------------------------------------- /4.弹出框/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 弹出框 8 | 83 | 84 | 85 |
86 | 87 |
88 |
89 |
90 |
91 | x 92 |

Hello Mask!

93 |
94 |
95 | 96 | 121 | 122 | -------------------------------------------------------------------------------- /5.Tab选项卡/example_02.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | CSS3外观漂亮淡入淡出Tab菜单演示 9 | 10 | 77 | 78 | 79 | 80 | 81 | 82 |
83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 |
97 | 98 |
99 |

明月何皎皎

100 |

明月何皎皎,照我罗床帏。 忧愁不能寐,揽衣起徘徊。 客行虽云乐,不如早旋归。 出户独彷徨,愁思当告谁! 引领还入房,泪下沾裳衣。 101 |

102 |
103 | 104 |
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 | 132 | 133 | -------------------------------------------------------------------------------- /6.调整窗口大小效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 调整窗口大小 8 | 9 | 45 | 46 | 47 | 48 |
49 |

Hello Window

50 |
51 | 52 | 138 | 139 | -------------------------------------------------------------------------------- /4.弹出框/READEME.md: -------------------------------------------------------------------------------- 1 | 这一节,我们来制作弹出框,这个组件在非常多的网页中都有用到,其实大多数用到的还是css动画。其实用JS也可以做,但是请记住能CSS写的尽量别用JS写,专业的东西交给专业的做,因为用JS写非常的消耗性能,写的不好就炸了,常见的基本都可以通过css完成。 2 | 3 | ## 首先准备HTML 4 | (慎重)这个HTML DOM结构 不好做css动画。正确的在后面。 5 | 6 | ```html 7 |
8 | 9 |
10 |
11 |
12 | x 13 |

Hello Mask!

14 |
15 |
16 | ``` 17 | 18 | ## 准备CSS 19 | 20 | ```css 21 | #mask{ 22 | position: fixed; 23 | left: 0; 24 | right: 0; 25 | top: 0; 26 | bottom: 0; 27 | background: rgba(0,0,0,.7); 28 | display: flex; 29 | justify-content: center; 30 | align-items: center; 31 | transition: all ease-in-out 1s; 32 | } 33 | 34 | #mask .box{ 35 | width: 800px; 36 | height: 400px; 37 | background: #f9f9f9; 38 | border: 1px solid #f7f7f7; 39 | border-radius: 5px; 40 | position: relative; 41 | } 42 | 43 | #mask .close{ 44 | position: absolute; 45 | right: 20px; 46 | top: 20px; 47 | display: block; 48 | width: 30px; 49 | height: 30px; 50 | line-height: 30px; 51 | text-align: center; 52 | cursor: pointer; 53 | border-radius: 50%; 54 | color: #ff7878; 55 | border: 1px solid; 56 | } 57 | 58 | #mask .box h1{ 59 | text-align: center; 60 | line-height: 400px; 61 | margin: 0; 62 | color: rgb(185, 185, 185); 63 | font-size: 80px; 64 | } 65 | ``` 66 | 67 | 这里我们终于用了flex居中,我只是为了让大家认识到居中有很多种方法,想认识更多,上google搜一下,非常多的人写了类似的文章。 68 | 69 | 假如按照目前的HTML结构和思路去完善,写是可以写出来,但是想加上动画就不行了,因为在同一个div里面,都是公用一个transition属性。 70 | 71 | 之所以留下这些错误,是让大家可以试着完善,真正体会下这些坑,我写的时候,也是感觉怀疑人生的。 72 | 73 | 这里我们换一个DOM结构,把 mask 分离开来。因为我们想在mask里面添加一个动画,还想在box里面添加一个动画,假如2个在一起就会发生重叠。而且`transition`是对`display:none`,变成`display:block`不起动画作用的。我们用`opacity/visibility`代替,之所以不随便用z-index是因为,这会造成一些bug。你可以尝试加上z-index试一试,你会发现当z-index为1的时候,按钮是白色的,其他地方是黑色的,这种体验就非常不好了。 74 | 75 | ```html 76 |
77 | 78 |
79 |
80 |
81 |
82 | x 83 |

Hello Mask!

84 |
85 |
86 | 87 | ``` 88 | 89 | 假如我们尝试把 ` transition: all .7s ;` 加到 `#dialog`里面,会发现动画不起作用了。 90 | 91 | ```css 92 | #mask, #dialog{ 93 | position: fixed; 94 | left: 0; 95 | right: 0; 96 | top: 0; 97 | bottom: 0; 98 | visibility: hidden; 99 | } 100 | 101 | #mask{ 102 | background: rgba(0,0,0,.7); 103 | opacity: 0; 104 | transition: all .7s ; 105 | } 106 | 107 | #dialog{ 108 | justify-content: center; 109 | align-items: center; 110 | display: flex; 111 | } 112 | 113 | #dialog .box{ 114 | width: 800px; 115 | height: 400px; 116 | background: #f9f9f9; 117 | border: 1px solid #f7f7f7; 118 | border-radius: 5px; 119 | transition: all .7s ; 120 | position: relative; 121 | top: -900px; 122 | } 123 | 124 | #mask.show{ 125 | opacity: 1; 126 | visibility: visible; 127 | 128 | } 129 | 130 | #dialog.show{ 131 | display: flex; 132 | visibility: visible; 133 | } 134 | 135 | #dialog.show .box{ 136 | top: 0px; 137 | } 138 | 139 | #dialog .close{ 140 | position: absolute; 141 | right: 20px; 142 | top: 20px; 143 | display: block; 144 | width: 30px; 145 | height: 30px; 146 | line-height: 30px; 147 | text-align: center; 148 | cursor: pointer; 149 | border-radius: 50%; 150 | color: #ff7878; 151 | border: 1px solid; 152 | } 153 | 154 | #dialog .close:hover{ 155 | color:red; 156 | } 157 | 158 | #dialog .box h1{ 159 | text-align: center; 160 | line-height: 400px; 161 | margin: 0; 162 | color: rgb(185, 185, 185); 163 | font-size: 80px; 164 | } 165 | ``` 166 | 167 | JS逻辑其实就是添加类名。 168 | 169 | ```js 170 | const loginBtn = document.querySelector('#login'); 171 | const mask = document.querySelector('#mask'); 172 | const closeBtn = document.querySelector('#dialog .close'); 173 | const dialog = document.querySelector('#dialog'); 174 | 175 | function close(){ 176 | dialog.classList.remove('show'); 177 | mask.classList.remove('show') 178 | } 179 | 180 | function open(){ 181 | dialog.classList.add('show'); 182 | mask.classList.add('show') 183 | } 184 | 185 | loginBtn.addEventListener('click', open); 186 | 187 | closeBtn.onclick = close 188 | 189 | dialog.onclick = (e) => { 190 | if(e.target.id == 'dialog') close(); 191 | } 192 | ``` -------------------------------------------------------------------------------- /10.鼠标右键菜单效果/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Menu 8 | 9 | 48 | 49 | 50 | 51 | 78 | 79 | 171 | 172 | -------------------------------------------------------------------------------- /10.鼠标右键菜单效果/readme.md: -------------------------------------------------------------------------------- 1 | 1.首先准备我们的HTML 2 | 3 | ```html 4 | 31 | ``` 32 | 33 | 34 | 2.美化一下我们的样式 35 | 36 | 37 | ```css 38 | *{padding: 0;margin: 0;} 39 | 40 | .menu{ 41 | position: absolute; 42 | display: none; 43 | } 44 | 45 | .menu ul{ 46 | list-style: none; 47 | background: #eee; 48 | } 49 | 50 | ul ul{ 51 | display: none; 52 | position: absolute; 53 | } 54 | 55 | ul li{ 56 | padding-left: 10px; 57 | padding-right: 20px; 58 | white-space: nowrap; 59 | height: 40px; 60 | line-height: 40px; 61 | box-sizing: border-box; 62 | border-bottom: 1px solid #999; 63 | cursor: pointer; 64 | } 65 | 66 | li.hasNext::after{ 67 | content: ' > '; 68 | position: absolute; 69 | right: 5px; 70 | } 71 | 72 | li.active > ul{ 73 | display: block; 74 | } 75 | ``` 76 | 77 | 78 | 3.JS逻辑 79 | 80 | ```js 81 | // mouseout -> inner li mouseover (错误点) -> outer li mouseover 错误的顺序 82 | // mouseout -> outer li mouseover -> inner li mouseover 正常顺序 83 | // inner li mouseover -> outer li mouseover 正常顺序 84 | ``` 85 | 86 | 这个你需要注意, 当我们是要冒泡的时候 是第一个事件触发顺序,所以会出错,我们应该使用第二种,或者第三种顺序。 87 | 88 | 第二种是使用定时器的逻辑。 89 | 90 | 第三种就是不使用事件冒泡,使用事件捕获,也就是当前使用的方式。 91 | 92 | ```js 93 | function setWidth(obj) { // 设置obj的宽度,拿到下面li标签里面最宽的,设置为ul的宽度 94 | let maxWidth = 0; 95 | 96 | for (i = 0; i < obj.children.length; i++) { 97 | let oLi = obj.children[i]; 98 | let iWidth = oLi.clientWidth; 99 | if (iWidth > maxWidth) maxWidth = iWidth; 100 | } 101 | 102 | for (i = 0; i < obj.children.length; i++) obj.children[i].style.width = maxWidth + "px"; 103 | } 104 | 105 | const liNodeList = document.querySelectorAll('li'); 106 | 107 | const menu = document.querySelector('.menu'); 108 | 109 | Array.from(liNodeList).forEach( li => { 110 | const innerUlDom = li.querySelector('ul'); 111 | innerUlDom && li.classList.add('hasNext'); // 当 li 下面有 ul标签的时候,添加一个小箭头的样式 112 | 113 | // let clearTimer, showTimer; 114 | 115 | li.addEventListener('mouseover', (e) => { 116 | if(innerUlDom){ 117 | // clearTimer && clearTimeout(clearTimer) 118 | // showTimer = setTimeout(() => { 119 | li.classList.add('active'); 120 | 121 | setWidth(innerUlDom); // 设置当前UL的宽度 122 | 123 | let top = li.offsetTop; // 当前的top,就为li所在的top值 124 | let left = li.offsetWidth; // lef 就是li的宽度 125 | 126 | innerUlDom.style.left = left + 'px'; 127 | innerUlDom.style.top = top + 'px'; 128 | 129 | 130 | // }, 300); 131 | 132 | } 133 | }, true); // 阻止冒泡,让逻辑正常 134 | 135 | li.onclick = (e) => { 136 | e.stopPropagation(); 137 | console.log('你点击了', e.target); // 为每一个li绑定一下事件 138 | hidden(menu); 139 | } 140 | 141 | li.onmouseout = () => { // 鼠标移出的时候,隐藏菜单 142 | // showTimer && clearTimeout(showTimer) 143 | // clearTimer = setTimeout(() => { 144 | li.classList.remove('active'); // 鼠标移开去掉 active 类 145 | // }, 300); 146 | } 147 | 148 | // mouseout -> inner li mouseover (错误点) -> outer li mouseover 错误的顺序 149 | // mouseout -> outer li mouseover -> inner li mouseover 正常顺序 150 | // inner li mouseover -> outer li mouseover 正常顺序 151 | }); 152 | 153 | 154 | function hidden(target) // 隐藏菜单方法 155 | { 156 | target.style.display = 'none'; 157 | target.style.left = '-900px'; 158 | target.style.top = '-900px'; 159 | } 160 | 161 | 162 | window.onload = (e) => { 163 | document.oncontextmenu = (e) => { 164 | e.preventDefault(); 165 | 166 | menu.style.display = 'block'; // 显示出菜单栏 167 | 168 | const uls = document.querySelectorAll('ul'); 169 | setWidth(uls[0]); // 设置 ul 的宽度 170 | 171 | 172 | menu.style.left = e.pageX + 'px'; // 让菜单栏显示在鼠标所点的位置 173 | menu.style.top = e.pageY + 'px'; 174 | return false; 175 | } 176 | 177 | document.onclick = (e) =>{ 178 | console.log('你取消了菜单'); // 当在其他地方点击的时候,取消显示菜单 179 | hidden(menu); 180 | } 181 | } 182 | ``` -------------------------------------------------------------------------------- /6.调整窗口大小效果/README.md: -------------------------------------------------------------------------------- 1 | ## 调整窗口大小 2 | 3 | 窗口大小,我们可以非常方便的使用`width`、`height`调整,但是如何知道 `width`和`height`是一个问题? 4 | 5 | 在 Window 操作系统中,假如我们想要缩放,我们通常会把鼠标移动到窗口的右边栏,和底部边栏,以及右下边栏。 6 | 7 | 而且在不同的边栏,鼠标呈现的样式也是不一样的。当我们在右边栏的时候我们可以通过`cursor: e-resize;`模拟鼠标样式。 8 | 9 | 在底部边栏我们可以通过`cursor: s-resize;`来模拟样式,当我们是右下的时候可以用过`cursor: nw-resize;`模拟鼠标样式。 10 | 11 | 首先我们设想一种比较理想的情况,可以调整的盒子的左上角刚好就是页面的左上角,也就是坐标系的0点,横向的是x轴,竖着的是y轴。 12 | 13 | 当我们点击右边栏的时候,开始记录开始的点,同时也是开始设置盒子宽度的时候,之后我们可以在文档上面再监听一下鼠标的位置,通过事件的e.pageX属性可以拿到横坐标,其实这里的e.pageX就是我们的拖拽好了的width。 14 | 15 | 当然这是比较理想的情况下,通常我们的盒子并不一定是在左上角,此时我们需要把盒子左上角的点给计算出来。 16 | 17 | ![](https://dn-nwggkrtp.qbox.me/20772bc2f0698b6d6c32.png) 18 | 19 | 这里有张图,我们先看 x 轴上面的情况。当用户点击我们的右边栏的时候,当然这里的点击指的是`moursedown` 鼠标按下,因为在拖拽的过程中,我们鼠标是不会放开的。 此时我们就可以拿到事件 e.pageX 和 右边栏距离盒子最左边的距离 offsetLeft 。 20 | 21 | 他们俩个相减就得到了盒子左上角点的x轴的坐标。也就是我们蓝线的值。 22 | 23 | 当我们把这个左边栏拖到新的位置的时候,我们把新的 pageX 减去 蓝线的值,就可以得到红线的值,而这个红线,就刚好等于我们需要设置的值。 24 | 25 | 26 | 27 | 1. 首先准备 HTML 28 | 29 | ```html 30 |
31 |

Hello Window

32 |
33 | ``` 34 | 35 | 2.准备 CSS 样式 36 | 37 | ```css 38 | .panel{ 39 | position: relative; 40 | border: 1px solid #eee; 41 | width: 400px; 42 | height: 350px; 43 | margin: 100px; 44 | text-align: center; 45 | } 46 | 47 | .right-bar{ 48 | width: 10px; 49 | height: 100%; 50 | position: absolute; 51 | right: 0; 52 | top: 0; 53 | cursor: e-resize; 54 | } 55 | 56 | .bottom-bar{ 57 | height: 10px; 58 | width: 100%; 59 | position: absolute; 60 | bottom: 0; 61 | cursor: s-resize; 62 | } 63 | 64 | .right-bottom-bar{ 65 | height: 20px; 66 | width: 20px; 67 | right: 0; 68 | bottom: 0; 69 | position: absolute; 70 | cursor: nw-resize; 71 | } 72 | ``` 73 | 74 | 75 | 3.添加JS逻辑 76 | 77 | 首先添加控制栏 78 | 79 | ```js 80 | // 动态的添加控制栏 81 | function addControlSideBar(panel){ 82 | const rightBar = document.createElement('div'); 83 | const bottomBar = document.createElement('div'); 84 | const rightBottomBar = document.createElement('div'); 85 | 86 | rightBar.className = 'right-bar'; 87 | bottomBar.className = 'bottom-bar'; 88 | rightBottomBar.className = 'right-bottom-bar'; 89 | 90 | let controlArray = [ rightBar, bottomBar, rightBottomBar] ; 91 | 92 | controlArray.forEach((i) => { 93 | i.draggable = false; // 禁用拖拽 94 | panel.appendChild(i) 95 | }); 96 | 97 | return controlArray; 98 | } 99 | ``` 100 | 101 | 102 | 之后为控制栏添加监听器 103 | 104 | ```js 105 | const panel = document.querySelector('#resizeAble'); 106 | // 为控制栏添加监听 107 | function listenerControlSideBar(controlArray){ 108 | controlArray.forEach((v, k) => { 109 | v.addEventListener('mousedown', setPanelStatus , false) 110 | v.addEventListener('selectstart', () => (false), false); // 禁止被选中 111 | 112 | }) 113 | } 114 | 115 | let controlArray = addControlSideBar(panel); 116 | listenerControlSideBar(controlArray); 117 | ``` 118 | 119 | 完成我们的 setPanelStatus 当用户点击我们的控制栏触发的回调函数。 120 | 121 | ```js 122 | 123 | var move_to_x, move_to_y; // 移动后的新坐标 124 | 125 | var timer; // 不停设置面板宽度和高度的定时器 126 | 127 | let min_width = 300; 128 | let min_height = 250; // 最小的宽度和高度 129 | 130 | // 实时监听鼠标的状态 131 | document.onmousemove = (e) => { 132 | move_to_x = e.pageX; 133 | move_to_y = e.pageY; 134 | } 135 | 136 | // 设置面板的状态 137 | function setPanelStatus(e){ 138 | console.log(e); 139 | // 当鼠标按下那一刻执行这个函数 140 | let start_x = e.pageX - e.target.offsetLeft; 141 | let start_y = e.pageY - e.target.offsetTop; 142 | // (start_x, start,y) 盒子左上角的坐标值. 143 | 144 | // 此时只要我们不放鼠标,那么高度就会跟随 move_to_x, 和 move_to_y 一直设置。 这个一直设置,我们需要用一个定时器来实现。 145 | 146 | timer = setInterval(() => { 147 | const width = Math.max(min_width, move_to_x - start_x); 148 | const height = Math.max(min_height, move_to_y - start_y); 149 | 150 | // 判断我们按下的是哪个控制栏,根据类别来设置宽和高 151 | // 这里的 10 和 20 分别是 变量的宽度 152 | switch(e.target.className){ 153 | case 'right-bar': 154 | panel.style.width = width + 'px'; 155 | break; 156 | case 'bottom-bar':; 157 | panel.style.height = height + 'px'; 158 | break; 159 | case 'right-bottom-bar': 160 | panel.style.height = height + 'px'; 161 | panel.style.width = width + 'px'; 162 | break; 163 | } 164 | }, 10) 165 | } 166 | ``` 167 | 168 | 169 | 之后我们完成我们的当用户放开鼠标,取消继续设置宽度和高度的定时器 170 | 171 | ```js 172 | document.onmouseup = document.ondrag = (e) => { // ondrag 是拖拽时间,有的时候把边栏当做拖拽的时候,没有及时清除 timer 就会出现 bug 173 | clearInterval(timer); 174 | timer = null; 175 | } 176 | ``` 177 | 178 | 179 | 有的时候,可能title 会被选中 180 | 181 | ```js 182 | panel.onselectstart = () => (false); // 禁止面板中被选中 183 | ``` 184 | 185 | 186 | OK,这样我们就完成了我们的逻辑。 -------------------------------------------------------------------------------- /3.瀑布流/example_02.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS 瀑布流 8 | 36 | 37 | 38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
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 | 205 | 206 | -------------------------------------------------------------------------------- /2.日历选择器/example_01.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 日历选择组件 8 | 79 | 80 | 81 |
82 | 83 |
84 | 113 |
114 |
115 | 116 | 247 | 248 | -------------------------------------------------------------------------------- /5.Tab选项卡/README.md: -------------------------------------------------------------------------------- 1 | ## CSS :hover 实现 Tab 切换选项卡 2 | 3 | 这里我们主要使用`:hover`伪类选择器 与 `~` 兄弟选择器 与 `nth-of-type` 选择器实现 Tab 选项卡。 4 | 5 | 能实现的功能不多,假如能满足需要,使用这个方法是最简单的。 6 | 7 | 1. 准备一下 HTML 8 | 9 | ```html 10 |
11 | 1 12 |
13 |

Hello Tab

14 | baidu.com 15 |
16 | 2 17 |
222
18 | 3 19 |
333
20 | 4 21 |
4444
22 |
23 | ``` 24 | 25 | 2. 准备css 26 | 27 | ```css 28 | .tab{ 29 | width: 800px; 30 | height: 600px; 31 | position: relative; 32 | margin: 0 auto; 33 | background: #f7f7f7; 34 | } 35 | 36 | .content:first-of-type{ 37 | display: block; 38 | z-index: 1; 39 | } 40 | 41 | .one:hover ~ .content:nth-of-type(1), 42 | .two:hover ~ .content:nth-of-type(2), 43 | .three:hover ~ .content:nth-of-type(3), 44 | .four:hover ~ .content:nth-of-type(4) 45 | { 46 | display: block; 47 | z-index: 9; 48 | background: #f7f7f7; 49 | } 50 | 51 | 52 | .one ~ .content:nth-of-type(1):hover, 53 | .two ~ .content:nth-of-type(2):hover, 54 | .three ~ .content:nth-of-type(3):hover, 55 | .four ~ .content:nth-of-type(4):hover{ 56 | display: block; 57 | z-index: 9; 58 | background: #f7f7f7; 59 | } 60 | 61 | 62 | .top{ 63 | width: 100px; 64 | height: 30px; 65 | line-height: 30px; 66 | display: block; 67 | text-align: center; 68 | float: left; 69 | background: #f5f5f5; 70 | cursor: pointer; 71 | } 72 | 73 | .content{ 74 | position: absolute; 75 | top: 30px; 76 | left: 0; 77 | right: 0; 78 | bottom: 0; 79 | display: none; 80 | } 81 | ``` 82 | 83 | ## css 借助 input 来实现选项卡 84 | 85 | 当我们为label,配置了for的时候,只要我们点击 label 就可以选中input。 86 | 87 | 而选中的input,我们可以通过`:checked`伪类选择器来选中它。 88 | 89 | ```html 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ``` 99 | 100 | 大家可以随便新建一个html,试一下上面的代码,然后改掉for,看看点击label还能不能成功。 101 | 102 | 1. 言归正传,我们开始准备HTML 103 | 104 | 105 | ```html 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 | 对酒当歌,人生几何!譬如朝露,去日苦多。 慨当以慷,忧思难忘。何以解忧?唯有杜康。 青青子衿,悠悠我心。但为君故,沉吟至今。 呦呦鹿鸣,食野之苹。我有嘉宾,鼓瑟吹笙。 明明如月,何时可掇?忧从中来,不可断绝。 越陌度阡,枉用相存。契阔谈讌,心念旧恩。(谈讌 132 | 一作:谈宴) 月明星稀,乌鹊南飞。绕树三匝,何枝可依? 山不厌高,海不厌深。周公吐哺,天下归心。(海 一作:水) 133 |

134 |
135 | 136 |
137 |

七哀诗三首·其一

138 |

139 | 西京乱无象,豺虎方遘患。 复弃中国去,委身适荆蛮。 亲戚对我悲,朋友相追攀。 出门无所见,白骨蔽平原。 路有饥妇人,抱子弃草间。 顾闻号泣声,挥涕独不还。 “未知身死处,何能两相完?” 驱马弃之去,不忍听此言。 南登霸陵岸,回首望长安, 悟彼下泉人,喟然伤心肝。 140 |

141 | 142 |
143 | 144 |
145 |

入若耶溪

146 |

艅艎何泛泛,空水共悠悠。 阴霞生远岫,阳景逐回流。 蝉噪林逾静,鸟鸣山更幽。 此地动归念,长年悲倦游。 147 |

148 | 149 |
150 | 151 |
152 | 153 |
154 | ``` 155 | 156 | 157 | 2.准备 CSS 结构 158 | 159 | ```css 160 | body { 161 | font-family: Cambria, Arial; 162 | background: #555; 163 | } 164 | 165 | .tabs { 166 | width: 100%; 167 | max-width: 600px; 168 | margin: 50px auto; 169 | } 170 | 171 | input { 172 | opacity: 0; 173 | } 174 | 175 | label { 176 | cursor: pointer; 177 | background: #999; 178 | color: #fff; 179 | border-radius: 4px 4px 0 0; 180 | padding: 1.5% 3%; 181 | float: left; 182 | margin-right: 2px; 183 | } 184 | 185 | label:hover { 186 | background: palegreen; 187 | } 188 | 189 | input:checked+label { 190 | background: palegreen; 191 | color: blueviolet; 192 | } 193 | 194 | .tabs input:nth-of-type(1):checked~.panels .panel:first-child, 195 | .tabs input:nth-of-type(2):checked~.panels .panel:nth-child(2), 196 | .tabs input:nth-of-type(3):checked~.panels .panel:nth-child(3), 197 | .tabs input:nth-of-type(4):checked~.panels .panel:last-child { 198 | opacity: 1; 199 | transition: .9s; 200 | } 201 | 202 | .panels { 203 | float: left; 204 | clear: both; 205 | position: relative; 206 | width: 100%; 207 | background: #fff; 208 | border-radius: 0 10px 10px 10px; 209 | min-height: 315px; 210 | } 211 | 212 | .panel { 213 | width: 100%; 214 | opacity: 0; 215 | position: absolute; 216 | background: #fff; 217 | border-radius: 0 10px 10px 10px; 218 | padding: 4%; 219 | box-sizing: border-box; 220 | } 221 | 222 | .panel h2 { 223 | margin: 0; 224 | } 225 | ``` 226 | 227 | 使用这样的也行,`.tabs input:nth-of-type(1):checked~.panels .panel:nth-of-type(1)`,你应该清楚`of-type`与`child`的区别。其实原理跟第一个差不多,只是多了一个input来记录状态,比`hover`来的更好。 228 | 229 | 230 | ### 使用 JS 实现Tab选项框 231 | 232 | 1.准备html结构 233 | ```html 234 |
235 | 241 |
242 |
243 |

山有木兮木有枝,心悦君兮君不知。

244 |
245 |
2
246 |
3
247 |
4
248 |
249 |
250 | ``` 251 | 252 | 253 | 2.加上css 254 | 255 | ```css 256 | *{ 257 | margin: 0; 258 | padding: 0; 259 | } 260 | .tabs{ 261 | width: 680px; 262 | padding: 5px; 263 | margin: 50px auto; 264 | } 265 | 266 | .menus{ 267 | height: 40px; 268 | width: 100%; 269 | } 270 | 271 | .menu{ 272 | height: 40px; 273 | line-height: 40px; 274 | width: 120px; 275 | border: none; 276 | outline: none; 277 | margin-right: -5px; 278 | border-radius: 5px 5px 0 0; 279 | } 280 | 281 | .menu.checked{ 282 | background: #F7A3A3; 283 | } 284 | 285 | .panels{ 286 | background: buttonface; 287 | min-height: 315px; 288 | border-radius: 0 10px 10px 10px; 289 | position: relative; 290 | } 291 | 292 | .panel{ 293 | width: 100%; 294 | opacity: 0; 295 | transition: .7s; 296 | position: absolute; 297 | } 298 | 299 | .panel.show{ 300 | opacity: 1; 301 | } 302 | ``` 303 | 304 | 305 | 3.JS添加 css class 逻辑。 306 | 307 | 这里我们需要通过`Array.from`将 NodeList 转化为 Array 类型。 308 | 309 | ```js 310 | function init(){ 311 | let panels = Array.from(document.querySelectorAll('.panel')); 312 | let menus = Array.from(document.querySelectorAll('.menu')); 313 | 314 | showTab(panels, menus, 0); // 显示第一页 315 | 316 | menus.forEach((menu, index) => { 317 | menu.onclick = () => { 318 | clearCSS(panels, menus); // 清楚所有css 319 | showTab(panels, menus, index); 320 | } 321 | }); 322 | } 323 | 324 | // 清楚所有再显示的 css class 325 | function clearCSS(panels, menus){ 326 | menus.forEach((p) => p.classList.remove('checked')); 327 | panels.forEach((p) => p.classList.remove('show')); 328 | } 329 | 330 | function showTab(panels, menus, index){ 331 | panels[index].classList.add('show'); 332 | menus[index].classList.add('checked') 333 | } 334 | 335 | init(); 336 | ``` -------------------------------------------------------------------------------- /3.瀑布流/README.md: -------------------------------------------------------------------------------- 1 | # CSS 瀑布流 2 | 3 | CSS3里面提供了一个控制列的属性,`column-width` 表示列的宽度, 而 `column-gap` 表示列与列之间的距离。 4 | 5 | 1.准备HTML与一些图片 6 | 7 | ```html 8 |
9 |
10 | 11 |
12 |
13 | 14 |
15 |
16 | 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 |
25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 | 35 |
36 |
37 | 38 |
39 |
40 | ``` 41 | 42 | 2. 准备CSS样式 43 | 44 | ```css 45 | *{padding: 0;margin: 0;} 46 | .container{ 47 | column-width: 350px; 48 | column-gap: 20px; 49 | padding: 0 20px; 50 | } 51 | 52 | .container div{ 53 | width: 350px; 54 | overflow: hidden; 55 | margin-bottom: 10px; 56 | box-shadow: 0px 1px 1px 1px rgba(0,0,0,.21); 57 | } 58 | 59 | div img{ 60 | width: 100%; 61 | } 62 | 63 | .container div{ 64 | max-width: 100%; 65 | padding: 10px; 66 | box-sizing: border-box; 67 | } 68 | ``` 69 | 70 | 代码量可以说非常的少,对于一些品质要求比较高的网站来说,这个方法并不是那么完善,它的排列顺序是上下左右的,可以看到阴影也有问题,不过未来的CSS可能更加强大。 71 | 72 | 73 | ## 使用JS动态计算实现瀑布流 74 | 75 | 这里我先把实现瀑布流的流程给叙述一遍,增加大家的印象,这样我们就可以有迹可循的写出瀑布流。 76 | 77 | 无论是什么瀑布流,它们的宽度都是相等,而高度是根据宽度不同,高度自动调整的。 78 | 79 | 我们为了实现瀑布流居中,可以使用`margin: 0 auto;`,但是使用它的规制必须是宽度固定的,当然我们也可以使用其他布局方法比如`flex`,其实实现一些小功能有很多种方法,而多种方法组合起来,就是解决一个大功能的方法。 80 | 81 | 那么我们如何知晓它的宽度呢? 82 | 83 | 屏幕宽度我们是否可以知道呢? 是的,我们可以通过`document.body.clientWidth`、`document.documentElement.clientWidth`获得它屏幕的宽度。 84 | 85 | 那么获取一个div盒子的宽度呢?(注意 offsetWidth 是不包括 margin 的),这里我们不给盒子增加任何 margin,而是再添加一层 div 使用 padding 来替代 margin 效果(不加就要自己加上margin,不过自己要通过js获取自己加一下,俩者优劣,仁者仁,智者智)。所以我们这里直接通过 offsetWidth 来拿到div的宽度。 86 | 87 | 现在我们已知总宽度,和一个盒子的宽度,问我们最多能放多少个盒子,那么这些盒子一起的宽度是多少呢?(没错,这就是一个小学数学应用题)。 88 | 89 | 假设我们的屏幕宽度为`let clientWith`, 一个盒子的宽度为 `let oneBoxWidth`。 最多能放 ` let canLayBoxNum = Math.floor(clientWidth % oneBoxWidth) `, 此时我们可能还要往下取整,我们可以使用`Math.floor`,有的朋友可能不熟悉`floor`方法,我们可以把`floor`看成`footer`也就是脚,通常脚是在下面的,所以就是往下取整。那么一起的宽度为 `let allBoxWidth = oneBoxWidth * canLayBoxNum` 90 | 91 | 现在我们解决了我们的第一个小问题,如何居中。 92 | 93 | 其实这并不是教大家如何写代码,终有一天你写任何代码都没问题,但是解决问题的方法,与按部就班的思考才是精髓,这不仅体现在编程中,还体现在你所有的现实生活中。 94 | 95 | 从第一个例子,我们现在已经非常清楚瀑布流的特效了,第一行是没有任何问题的,只要我们使用`float:left;` 就可以了,但是第二行开始,它会认为第一行的高度为,第一行里面高度最高的盒子。所以第二行与第一行之间将会有参差不齐的间隙,现在我们的问题就是解决这个现象?让盒子出现在我们想要的位置,我们通常用的方法就是`postion:relative`的父元素,`postion:absolute`的子元素,那么它的 `left` 和 `top` 96 | 又应该是多少呢?我们如何知晓呢? 97 | 98 | 首先我们正常考虑,(此时我建议你在你的心里面构建一个矩阵,或者说棋盘。)把第二行的第一个盒子,放在第一行,第一个盒子的下面,拿到第一行第一个盒子的高度做为下面盒子的 top,left相同即可,但是这样有没有考虑这样一个问题,每一次第一个盒子的高度都是最短的呢? 要是来个20横行,那不就出现了第一竖列短的要命,其他的长的不像话,这样不好了。 99 | 100 | 所以我们应该是在上一行高度最小的地方放置下一行的盒子,所以此时我们需要一个数组,来存储每一竖列当前的高度,而这个每竖列当前的高度,恰好可以作为我们当前添加盒子的`top`,而left,我们只需要知道它是在第几列就行了,`第几列 * 盒子的宽度 = left`。每次加上了一个新的盒子,是不是我们要更新当前的列高度啊,直接加上当前加上盒子的高度集合,然后接着找高度最小的列,继续添加盒子。 101 | 102 | ### 首先准备HTML 103 | 我们直接使用 Emmet 插件直接生成, 要不然写30个得累死。 104 | 105 | ```css 106 | div#main>(.box>.pic>img[src='./img/$.jpg'])*30 107 | ``` 108 | 109 | 接着按下Tab键就行了。 110 | 111 | ### 再来点好看的样式 112 | 113 | ```css 114 | #main{ 115 | position: relative; 116 | } 117 | 118 | .box{ 119 | float: left; 120 | padding: 15px 0 0 15px; 121 | } 122 | 123 | .pic{ 124 | padding: 10px; 125 | border: 1px solid #ccc; 126 | box-shadow: 0 0 3px #ccc; 127 | border-radius: 5px; 128 | } 129 | ``` 130 | 131 | ### JS 开工 132 | 133 | 首先我们解决居中问题 134 | 135 | ```js 136 | function init(mainSelector, boxSelector){ 137 | const main = document.querySelector(mainSelector); 138 | const boxArray = document.querySelectorAll(boxSelector); 139 | const boxCanLayNumber = Math.floor(document.documentElement.clientWidth / boxArray[0].offsetWidth); 140 | const mainWidth = boxCanLayNumber * boxArray[0].offsetWidth; 141 | main.style.cssText = `width:${mainWidth}px;margin:0 auto;`; 142 | } 143 | 144 | init('#main', '.box') 145 | ``` 146 | 147 | 紧接着我们完善我们的瀑布流逻辑, 修改一些变量和函数名,让其更容易被阅读 148 | 149 | ```js 150 | function render(mainSelector, boxSelector){ 151 | const main = document.querySelector(mainSelector); 152 | const boxArray = document.querySelectorAll(boxSelector); 153 | const oneBoxWidth = boxArray[0].offsetWidth; 154 | const boxCanLayNumber = Math.floor(document.documentElement.clientWidth / oneBoxWidth); 155 | const mainWidth = boxCanLayNumber * oneBoxWidth; 156 | main.style.cssText = `width:${mainWidth}px;margin:0 auto;`; 157 | 158 | let boxHeightArray = []; 159 | 160 | boxArray.forEach((box, index) => { // 遍历计算每一个盒子的left和top,并设置它们。 161 | if(index < boxCanLayNumber){ // 假如是第一行,那么直接就存储到数组里面 162 | boxHeightArray[index] = box.offsetHeight; 163 | }else{ 164 | const minHeightValue = Math.min(...boxHeightArray); // 找到列里面最小的值。 165 | const minHeightIndex = boxHeightArray.findIndex((v) => v == minHeightValue); // 当前最小列的索引值 166 | box.style.position = 'absolute'; 167 | box.style.left = minHeightIndex * oneBoxWidth + 'px'; // 当minHeightIndex为 0 刚好,left 就为 0; 168 | // box.style.left = boxArray[minHeightIndex].offsetLeft + 'px'; // 跟每一列第一个的左偏移量相同也可 169 | box.style.top = minHeightValue + 'px'; 170 | boxHeightArray[minHeightIndex] += box.offsetHeight; // 增加当前列的高度 171 | } 172 | }); 173 | } 174 | 175 | render('#main', '.box') 176 | ``` 177 | 178 | 此时,我们假如快速的刷新几次,我们会发现有一些问题,图片发生重叠,出现错误,接下来如何判断我完全是依照我自己的经验来的,我感觉` boxHeightArray[minHeightIndex] += box.offsetHeight` 这一行出现了问题,后来思考了一下,高度的问题,会不会是图片还没有加载完成,就直接计算了,导致高度不准确,所以我们把`render`放到`window.onload`里面去就可以了。 179 | 180 | 181 | ### 通常瀑布流都是无限加载的 182 | 183 | 我们把剩下的加载逻辑完成。逻辑也是非常的简单,当用户滚动的时候,滚到最后一个元素距离顶部的位置的时候,开始加载图片。 184 | 185 | 以下是全部代码。 186 | 187 | ```js 188 | function render(mainSelector, boxSelector){ 189 | 190 | const main = document.querySelector(mainSelector); 191 | const boxArray = document.querySelectorAll(boxSelector); 192 | const oneBoxWidth = boxArray[0].offsetWidth; 193 | const boxCanLayNumber = Math.floor(document.documentElement.clientWidth / oneBoxWidth); 194 | const mainWidth = boxCanLayNumber * oneBoxWidth; 195 | main.style.cssText = `width:${mainWidth}px;margin:0 auto;`; 196 | 197 | let boxHeightArray = []; 198 | 199 | boxArray.forEach((box, index) => { 200 | console.log(boxHeightArray); 201 | if(index < boxCanLayNumber){ // 假如是第一行,那么直接就存储到数组里面 202 | boxHeightArray[index] = box.offsetHeight; 203 | }else{ 204 | const minHeightValue = Math.min(...boxHeightArray); // 找到列里面最小的值。 205 | const minHeightIndex = boxHeightArray.findIndex((v) => v == minHeightValue); // 当前最小列的索引值 206 | box.style.position = 'absolute'; 207 | box.style.left = minHeightIndex * oneBoxWidth + 'px'; // 当minHeightIndex为 0 刚好,left 就为 0; 208 | // box.style.left = boxArray[minHeightIndex].offsetLeft + 'px'; // 跟每一列第一个的左偏移量相同也可 209 | box.style.top = minHeightValue + 'px'; 210 | boxHeightArray[minHeightIndex] += box.offsetHeight; 211 | } 212 | }); 213 | 214 | 215 | } 216 | 217 | const arrayLast = (arr) => arr[arr.length - 1] 218 | 219 | function shouldLoad(mainSelector, boxSelector){ 220 | const main = document.querySelector(mainSelector); 221 | const boxArray = document.querySelectorAll(boxSelector); 222 | const scrollTop = document.body.scrollTop; // 当前滚动所处的位置 223 | const clientHeight = document.documentElement.clientHeight; // 一个页面高度,第一屏的高度。 224 | const lastBoxHeight = arrayLast(boxArray).offsetTop; // 最后一个元素距离顶部的距离 225 | return lastBoxHeight < scrollTop + clientHeight; 226 | // 当第一屏的高度加上滚动的高度,再减去一个容错的 300 大于最后一个盒字距离顶部的距离的时候 227 | // scrollTop 是不包括当前可见的部分,所以要加上clientHeight可见部分的高度。 228 | } 229 | 230 | function loadImg(data, mainSelector){ 231 | const main = document.querySelector(mainSelector); 232 | data.forEach((img, index) => { 233 | // 创建 box 、将box假如到main中,创建pic,将pic添加到box中,创建img,将img添加到pic中 234 | let box = document.createElement('div'); 235 | box.className = 'box'; 236 | main.appendChild(box); 237 | let pic = document.createElement('div'); 238 | pic.classList.add('pic'); 239 | box.appendChild(pic); 240 | let imgDom = document.createElement('img'); 241 | imgDom.src = `./img/${img.src}`; 242 | pic.appendChild(imgDom); 243 | }); 244 | } 245 | 246 | 247 | window.onload = () => { 248 | render('#main', '.box'); 249 | 250 | window.onscroll = () => { 251 | console.log(shouldLoad('#main', '.box')); 252 | if(shouldLoad('#main', '.box')){ 253 | const data = [{src:'31.jpg'},{src:'32.jpg'},{src:'33.jpg'},{src:'34.jpg'},{src:'35.jpg'},{src:'36.jpg'},{src:'37.jpg'}]; 254 | // data 是模拟的假数据 255 | loadImg(data, '#main'); 256 | render('#main', '.box'); 257 | } 258 | } 259 | } 260 | ``` 261 | 262 | 因为有一张图比较大,所以我们添加一个css逻辑,固定以下宽度。 263 | 264 | ```css 265 | .pic{ 266 | padding: 10px; 267 | border: 1px solid #ccc; 268 | box-shadow: 0 0 3px #ccc; 269 | border-radius: 5px; 270 | max-width: 100%; 271 | } 272 | 273 | .pic img{ 274 | width: 287px; 275 | } 276 | ``` 277 | 278 | 好了,大功告成,自己滚一下看看吧。 279 | 280 | 所有代码例子都在我的 Github 可以找到。 -------------------------------------------------------------------------------- /1.滚动效果/readme.md: -------------------------------------------------------------------------------- 1 | # 列表滚动效果 2 | 制作列表滚动效果的核心就在于滚动框的`scrollTop`属性,它代表着我们滚动了多少像素。 3 | 当然为了让容器不显示滚动条我们会设置`overflow:hidden;` 来隐藏滚动条。 4 | 5 | ## 无缝滚动效果 6 | 7 | 1. 准备HTML结构 8 | ```html 9 |
10 |

诗词推荐

11 |
12 | 19 |
20 |
21 | ``` 22 | 23 | 2. 加上style样式 24 | 25 | ```css 26 | 47 | ``` 48 | 49 | 3. 书写我们的 JavaScript 代码逻辑 50 | 51 | ```javascript 52 | const listBox = document.querySelector('.list'); 53 | listBox.innerHTML += listBox.innerHTML; 54 | listBox.scrollTop = 0; 55 | const speed = 50; 56 | let counter = 0; 57 | 58 | const callback = () => { 59 | if(listBox.scrollTop >= listBox.scrollHeight / 2){ 60 | counter = 0; 61 | listBox.scrollTop = counter; 62 | }else{ 63 | listBox.scrollTop = ++counter; 64 | } 65 | } 66 | 67 | let timer = setInterval(callback, speed); 68 | 69 | 70 | listBox.addEventListener('mouseover', () => { 71 | clearInterval(timer); 72 | }); 73 | 74 | listBox.addEventListener('mouseout', () => { 75 | timer = setInterval(callback, speed); 76 | }); 77 | ``` 78 | 79 | **拿到listBox容器之后,我们要把里面的内容拷贝一份。你可能会问我为什么?** 80 | 81 | 当我们滚动到最底部的时候是不是要有一个新的替补呀?序号为5的内容后面应该跟着的是序号为1的内容,假如我们不拷贝一份的话,5后面是空白,当scrollTop为0的时候里面就跳到从1开始滚了,这样非常的突然,用户一定会认为这个网站出问题了。 82 | 83 | 大家可以注释拷贝内容这一行代码看看会是怎么个情况,学习最好的方法就是自己去体验,学会体验不一样的东西,做一个老司机,在控制台里面输入`listBox.scrollTop`, 你会发现`scrollTop`最大为`29.600000381469727`,所以说为了让我们有更多的内容可以滚动,拷贝一份即可。 84 | 85 | 然后我们先将 scrollTop 初始化为 0, 设置了一个滚动速度speed为50ms,以及一个存储定时器变量timer,还有counter计数器。 86 | 87 | **你可能感到困惑为什么不直接让scrollTop自己加加不就好了?** 88 | 89 | 是因为我发现在我的Chrome下面测试,scrollTop自己加加会莫名变成浮点数,导致后面判断是否滚到相应的位置无法判断,导致出现问题。 90 | 91 | 通过定时器 setInterval 每 50ms 让 scrollTop 加 + , 这样就形成了滚动,当用户把鼠标移动上去的时候,清除这个定时器,鼠标移动到容器框之外的时候又加上定时器让他继续滚动。 92 | 93 | 94 | **`listBox.scrollTop >= listBox.scrollHeight / 2` 这个条件大家可能不太好理解?** 95 | 96 | scrollTop 现在大家应该清楚是什么了,那 scrollHeight 呢? 97 | 98 | 遇见不懂的东西,我们首先要去猜测,之后再通过代码或者什么其他方法去验证我们的猜测,当实在找不到的时候我们再去查资料,这可以非常好的锻炼我们解决问题的能力,这是为了避免总会有一些问题是网络上找不到问题的。 99 | 100 | 首先我们通过 `scrollHeight` 单词来猜测,滚动高度,listBox 滚动的高度。 我们知道 `scrollTop` 是滚动上方,也就是说滚动距离容器顶部的距离,同时也是我们滚动了多少像素。此时还剩下什么高度呢? listBox 可以滚动的高度? listBox 里面内容的高度? 那么究竟是哪一个呢? 101 | 102 | 我们在控制台里面输入 `listBox.scrollHeight` , 我们发现是 `300` ? 不知道根据这个数值大家有没有猜到什么呢? 一个 li 的高度是30,一共有 5 个,然后我们又拷贝了一份。也就是说`30 * 5 * 2`,刚好就是我们的 `300`。 也就是说 `listBox.scrollHeight` 是我们容器里面内容的高度。 103 | 104 | **那么现在我问,想要知道可以滚动的高度是多少呢?** 105 | 106 | `可以滚动的高度 = 容器内容高度 - 可以看得到的高度`, 也就是 `300 - 120` 107 | 108 | ## 间歇滚动效果 109 | 110 | 间歇滚动,说明就是过一段时间再滚动。那么滚动我们一定会用到 setInterval , 那么过一会再滚呢?那就是用 setTimeout , 所以我们需要准备 2 个回调函数,一个用于 seInterval 和 setTimeout。 111 | 112 | setInterval 回调里面是滚动的逻辑,setTimeout里面的回调是创建 setInterval 的逻辑。 那么什么时候停止滚动呢?每一个的 li 高度是 30, 我们需要停的地方分别是 `30 60 90 120` 等等,这些都是可以被30整除的。 113 | 114 | 那么如何停止滚动呢? 清除 setInterval 的返回值 timer 就行了,然后再定义一个 setTimeout 让其 2s 后再通过 setInterval 创建一个 timer 开始滚动。 115 | 116 | 我们再来理清一下流程,首先 setTimeout 回调创建一个 setInterval 的 timer 开始滚动,滚动到 30 的时候,清除 timer ,然后再设置一个 setTimeout ,让其 2s 后再滚动。 117 | 118 | * JavaScript 逻辑 119 | 120 | HTML 和 CSS 与上面相同 121 | 122 | ```javascript 123 | const listBox = document.querySelector('.list'); 124 | listBox.innerHTML += listBox.innerHTML; 125 | listBox.scrollTop = 0; 126 | const itemHeight = 30; // li 高度 127 | const delay = 2000; // timeout 时长 128 | const speed = 50; // iterval 时长 129 | let timer; // 保存 iterval 引用 130 | let counter = 0; 131 | 132 | function init() { 133 | timer = setInterval(slide, speed); 134 | listBox.scrollTop = ++counter; // 先滚一点, 避免一直停在 counter % itemHeight == 0 135 | } 136 | 137 | function slide() { // 滚动逻辑 138 | if (counter % itemHeight == 0) { 139 | clearInterval(timer); 140 | setTimeout(init, delay) 141 | } else { 142 | listBox.scrollTop = ++counter; 143 | if (listBox.scrollTop >= listBox.scrollHeight / 2) { 144 | counter = 0; 145 | listBox.scrollTop = counter; 146 | } 147 | } 148 | } 149 | setTimeout(init, delay) 150 | ``` 151 | 152 | 153 | ## 回到顶部效果 154 | 155 | 1.准备 HTML 与 CSS 156 | 157 | **注意** : 不要设置 `href="#"` 这样会让程序失效,空锚链接会默认直接跳到顶部。当然这也算是最简单的顶部效果,就是体验不太好。 158 | 159 | ```html 160 | 165 |
166 | 回到顶部 167 | ``` 168 | 169 | 2.书写 JavaScript 逻辑 170 | 171 | 首先我们准备一个判断滚动方向的代码片段,这个也是我搜索的一个代码片段。 172 | 173 | 用第二次滚动的数值减去第一次滚动的数值,因为只有滚动的时候才会触发回调,所以俩次的数值通常是不同的,假如 > 0 就说明向下的,< 0 就是说明向上。 174 | 175 | ```js 176 | // 判断页面滚动的方向 177 | function scroll( fn ) { 178 | let beforeScrollTop = document.body.scrollTop, 179 | fn = fn || function() {}; 180 | doc.onscroll = () => { 181 | let afterScrollTop = document.body.scrollTop; 182 | let delta = afterScrollTop - beforeScrollTop; 183 | if( delta === 0 ) return false; 184 | fn( delta > 0 ? "down" : "up" ); 185 | beforeScrollTop = afterScrollTop; 186 | }; 187 | } 188 | ``` 189 | 190 | 然后我们准备一个渐进函数,这个函数的作用就是,返回一个a 趋近与 b 的值,而 rate 则是 每次趋近的比例。 191 | 192 | ``` 193 | function aToB( a , b , rate){ 194 | return a + ( b - a ) / rate; 195 | } 196 | ``` 197 | 198 | **完整的代码** 199 | 200 | 当我们明白了前面几个例子之后,这个滚动到顶部非常简单,只不过我们这次不是恒定减多少,而是用了一个趋近函数。 201 | 202 | 而且当我们在往上滚动的过程中,用户往下滚动,我们立刻停止自动往上滚动。 203 | 204 | ```js 205 | let doc = document.body ; 206 | let timer; 207 | 208 | function aToB( a , b , rate){ 209 | return a + ( b - a ) / rate; 210 | } 211 | 212 | // 判断页面滚动的方向 213 | function scroll( fn ) { 214 | let beforeScrollTop = document.body.scrollTop, 215 | fn = fn || function() {}; 216 | doc.onscroll = () => { 217 | let afterScrollTop = document.body.scrollTop; 218 | let delta = afterScrollTop - beforeScrollTop; 219 | if( delta === 0 ) return false; 220 | fn( delta > 0 ? "down" : "up" ); 221 | beforeScrollTop = afterScrollTop; 222 | }; 223 | } 224 | 225 | scroll((dir) => { 226 | if(dir == "down"){ 227 | // 当用户想停,往下面一滚就可以停住。 228 | clearInterval(timer); 229 | } 230 | }); 231 | 232 | let btn = document.querySelector('#back-top'); 233 | 234 | btn.onclick = () => { 235 | timer = setInterval(() => { 236 | doc.scrollTop = aToB(doc.scrollTop, 0 , 4); 237 | if(doc.scrollTop <= 1) clearInterval(timer); 238 | }, 50); 239 | } 240 | ``` 241 | 242 | ## 轮播 243 | 244 | 轮播的原理其实有非常多,这里我们主要使用`float:left;`以及`scrollLeft`来控制轮播,原理跟前面的间歇性滚动一样。 245 | 246 | 对于 CSS 与 HTML 有一部分有点难,我们先来准备下 HTML 247 | 248 | ```html 249 | 256 | 257 | ``` 258 | 259 | ```css 260 | *{ 261 | padding: 0; 262 | margin: 0; 263 | } 264 | 265 | .container, .container li { 266 | width: 400px; 267 | } 268 | 269 | .container{ 270 | overflow: hidden; 271 | margin: 20px auto; 272 | } 273 | 274 | ul{ 275 | width: 3200px; 276 | list-style: none; 277 | } 278 | 279 | ul:after{ 280 | content: ''; 281 | display: block; 282 | clear: both; 283 | } 284 | 285 | .container li{ 286 | float: left; 287 | text-align: center; 288 | } 289 | 290 | .container li img { 291 | width: 100%; 292 | } 293 | ``` 294 | 295 | 外层的`container`作为我们的可视容器,里面的`ul`作为我们的滚动容器。 296 | 297 | ```js 298 | let carousel = document.querySelector('#Carousel'); 299 | carousel.children[0].innerHTML += carousel.children[0].innerHTML; 300 | carousel.scrollLeft = 0; 301 | const itemWidth = 400; 302 | const delay = 2000; 303 | const speed = 20; 304 | let timer; 305 | let counter = 0; 306 | 307 | function init() { 308 | timer = setInterval(slide, speed); 309 | counter += 10; 310 | carousel.scrollLeft = counter; // 先滚一点, 避免一直停在 counter % itemWidth == 0 311 | } 312 | 313 | function slide() { // 滚动逻辑 314 | if (counter % itemWidth == 0) { 315 | clearInterval(timer); 316 | setTimeout(init, delay) 317 | } else { 318 | counter += 10; 319 | carousel.scrollLeft = counter; 320 | if (counter >= carousel.scrollWidth / 2) { 321 | counter = 0; 322 | carousel.scrollLeft = counter; 323 | } 324 | } 325 | } 326 | setTimeout(init, delay) 327 | ``` 328 | 329 | 只是改变了一下变量名称,与一些滚动速度。 330 | 331 | -------------------------------------------------------------------------------- /2.日历选择器/README.md: -------------------------------------------------------------------------------- 1 | # 日历选择器 2 | 3 | 首先我们想一下我们平常看到的日历选择器,通常头部会有一个星期几表头,下面就是日期,而日期下面通常会有1号,1号前面应该有一些上个月的日期,而最后一天后面还应该有下一个月的日期。 4 | 5 | 对于日期相关处理,JavaScript 提供了 Date 对象。 6 | 7 | ``` 8 | getDate 获得一个月的第几天 9 | getDay 获得一个星期的第几天 (0 - 6) 10 | getMonth 获取月份 (0 - 11) 11 | getFullYear 获取年份 12 | ``` 13 | 14 | 而对于比较 Date 对象的构造器有一个非常有意思的地方,那就是越界了之后,Date 会帮你自动校正日期。 15 | 16 | ``` 17 | new Date() 18 | Sun Apr 16 2017 13:25:50 GMT+0800 (中国标准时间) // 当前日期 4/16 19 | 20 | new Date(2017, 4, 1) 21 | Mon May 01 2017 00:00:00 GMT+0800 (中国标准时间) // 日期 5/1 日 22 | ``` 23 | 24 | 我们可以看到这里有一个陷阱,当我们给月份的参数是 4 的时候,其实它是五月份。 25 | 26 | 接下来我们来看一看日期的自动修正。 27 | 28 | ``` 29 | new Date(2017, 3, 0) 30 | Fri Mar 31 2017 00:00:00 GMT+0800 (中国标准时间) // 3 月 31 日 31 | 32 | // 4 月份的第 0 日,就是 3 月份的最后一日 33 | 34 | new Date(2017, 3, 1) 35 | Sat Apr 01 2017 00:00:00 GMT+0800 (中国标准时间) // 4 月 1 日 36 | 37 | 38 | new Date(2017, 4, 0) 39 | Sun Apr 30 2017 00:00:00 GMT+0800 (中国标准时间) // 4 月 30 日 40 | 41 | // 5 月份的 0 日,就是 4 月份的最后一日 42 | 43 | new Date(2017, 3, 31) 44 | Mon May 01 2017 00:00:00 GMT+0800 (中国标准时间) // 5 月 1 日 45 | 46 | 4 月份只有 30 日, 当为 31 日的时候,变成了 5月份的 1 日 47 | 48 | ``` 49 | 50 | 通过这些我们就可以获取当前月份的最后一天,以及上个月最后一天。 51 | 52 | 1. 首先我们准备一个基本的静态页面 53 | 54 | ```html 55 |
56 | 57 |
58 | 59 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 |
60 | < 61 | 2017 - 3 62 | > 63 | 64 |
1234567
85 |
86 |
87 | ``` 88 | 89 | ```css 90 | *{padding: 0;margin: 0;} 91 | .field{ 92 | width: 300px; 93 | margin: 20px auto; 94 | font-size: 12px; 95 | position: relative; 96 | } 97 | .field input{ 98 | width: 100%; 99 | box-sizing: border-box; 100 | outline: none; 101 | } 102 | .date-select{ 103 | background: #e8e8e8; 104 | color: #fff; 105 | cursor: pointer; 106 | transition: all .8s; 107 | position: absolute; 108 | width: 100%; 109 | z-index: 2; 110 | display: none; 111 | } 112 | 113 | .date-select table{ 114 | width: 100%; 115 | border-collapse: collapse; 116 | text-align: center; 117 | } 118 | 119 | 120 | .date-select table caption:after{ 121 | content: ''; 122 | display: block; 123 | clear: both; 124 | } 125 | 126 | .date-select table caption{ 127 | background: #999; 128 | line-height: 20px; 129 | } 130 | 131 | .date-select table th{ 132 | background: #999; 133 | } 134 | 135 | .date-select table tr:first-child{ 136 | line-height: 30px; 137 | } 138 | 139 | .date-select table tr td{ 140 | border: 1px solid #f0f0f0; 141 | } 142 | 143 | .date-select table tr:not(:first-child){ 144 | line-height: 25px; 145 | } 146 | 147 | .date-select table caption span#prev-month{ 148 | float: left; 149 | padding-left: 10px; 150 | } 151 | .date-select table caption span#next-month{ 152 | float: right; 153 | padding-right: 10px; 154 | } 155 | ``` 156 | 157 | ```js 158 | const prevMonthBtn = document.querySelector('#prev-month'), 159 | nextMonthBtn = document.querySelector('#next-month'); 160 | 161 | const dateInput = document.querySelector('#date-input'); 162 | 163 | const dateSelect = document.querySelector('.date-select'); 164 | 165 | dateInput.onclick = () => { 166 | if(dateSelect.style.display == "block" ){ 167 | dateSelect.style.display = "none"; 168 | return; 169 | } 170 | dateSelect.style.display = "block"; 171 | } 172 | 173 | 174 | prevMonthBtn.onclick = () => { 175 | console.log("prev"); 176 | } 177 | 178 | nextMonthBtn.onclick = () => { 179 | console.log("next"); 180 | } 181 | ``` 182 | 183 | 到目前为止,我们已经可以看到一个雏形了,但是这是有形无神的,现在我们再来准备我们的神魂精华,也就是获取日期的逻辑。 184 | 185 | ```js 186 | function get42ArrayDate(year, month){ 187 | // month 是正常月份 188 | let now = year && month? new Date(year, month -1 ) : new Date(); 189 | let _year = now.getFullYear(); 190 | let _month = now.getMonth(); // Date 的月份比正常的月份小 1 191 | 192 | let firstDayDateObj = new Date(_year, _month, 1); // 当月第一天日期 193 | 194 | let _day = firstDayDateObj.getDay() == 0 ? 7 : firstDayDateObj.getDay(); // 修正一下星期的日期,当为 0 的时候说明为星期天 195 | 196 | let needFillDayCount = _day - 1; // 我们需要补的日期, 当我们一号为星期天的时候,也就是 _date 为 7,则我们需要把星期一到星期六的天数给补完 197 | 198 | let prevMonthLastDayDateObj = new Date(_year, _month, 0); // 上一个月最后一天的日期 199 | let currentMonthLastDayDateObj = new Date(_year, _month + 1, 0); // 当前月最后一天的日期 200 | 201 | let arr = Array.from({length:42}, (v, k) => k - needFillDayCount + 1 ) ; // 需要填充的数组 0 - 41 = 42 位 再加 1 是为了让 K 从 1开始 202 | console.log(arr); 203 | let ret_arrObj = arr.map((val, index) => { 204 | // 现在我们对 负数日期,和超出限定的日期进行修正处理 205 | // 我们留下这个负数和超出的是为了让其他地方也可以使用这些数据通过 Date 直接构造正确的日期 206 | // 首先修正负数, 并且把正确的放在一个对象里面 207 | let prevMonthLastDay = prevMonthLastDayDateObj.getDate(); // 上个月最后一天的日期 208 | let currentMonthLastDay = currentMonthLastDayDateObj.getDate(); // 当前月最后一天 209 | let ret_obj = { 210 | index: val 211 | }; 212 | if( val <= 0 ){ 213 | // 对负数日期进行修正 214 | ret_obj.showDate = prevMonthLastDay + val; // 此时的 val 是一个负数,当 -0 的时候就是上月最后一天, - 1就是倒数第二天,以此类推。 215 | ret_obj.showMonth = _month; // showMonth 顾名思义应该为我们所理解的月份,也就是从1开始的,而 _month 是从 0 开始的,所以我们应该 + 1, 而这里是上个月的,所以要 -1 所以刚好抵消 216 | }else if (val > currentMonthLastDay){ 217 | // 对超出日期进行修正 218 | ret_obj.showDate = val - currentMonthLastDay ; // 此时的 val 是超出正常日期的,我们让这个数减去单月的最后一天的值,相当于我们此时重新开始从 1 开始计数。 32 - 31 为 1号。 33 -31 为 2号, 以此类推。 219 | ret_obj.showMonth = _month + 2; 220 | }else{ 221 | // 正常的本月日期 222 | ret_obj.showDate = val; 223 | ret_obj.showMonth = _month + 1; 224 | } 225 | 226 | return ret_obj; 227 | }); 228 | 229 | return { 230 | currentMonth: _month + 1, 231 | currentYear: _year, 232 | dateArray: ret_arrObj 233 | } 234 | 235 | } 236 | 237 | console.dir(get42ArrayDate()); 238 | ``` 239 | 240 | 解释都在注释里面,仔细看一下注释,然后自己运行一下,看看结果正确不正确。 241 | 242 | 243 | 接下来我们要把一些鼠标点击的事件,必须要等到已经渲染完HTML结构之后才能绑定,所以绑定的逻辑我们需要修改一下位置。 244 | 245 | 顺便我们把我们的渲染逻辑给写好,如下。 246 | 247 | 为了把当月的日期明显一点,我们增加一点样式 248 | 249 | ```css 250 | .date-select table tr td.current-month-day{ 251 | background: #d6d6d6; 252 | } 253 | 254 | ``` 255 | 256 | ```js 257 | function render(date){ 258 | let code = ` 259 | 260 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | `; 277 | 278 | date.dateArray.forEach((item, index) => { 279 | if(index % 7 == 0) { 280 | code += '' 281 | } 282 | code += `` 283 | }); 284 | 285 | dateSelect.innerHTML = code; 286 | 287 | const prevMonthBtn = document.querySelector('#prev-month'), 288 | nextMonthBtn = document.querySelector('#next-month'); 289 | 290 | prevMonthBtn.onclick = () => { 291 | let month = date.currentMonth - 1; 292 | let year = date.currentYear; 293 | let prevMonthDate = get42ArrayDate(year, month); 294 | render(prevMonthDate); // 重新渲染数据 295 | } 296 | 297 | nextMonthBtn.onclick = () => { 298 | let month = date.currentMonth + 1; 299 | let year = date.currentYear; 300 | let nextMonthDate = get42ArrayDate(year, month); 301 | render(nextMonthDate); // 重新渲染数据 302 | } 303 | 304 | dateSelect.addEventListener('click', (e) => { 305 | let targetDom = e.target; 306 | console.dir(targetDom) 307 | if(targetDom.tagName == 'SPAN' || targetDom.tagName != 'TD') return; 308 | let selectDay = new Date(date.currentYear, date.currentMonth - 1, targetDom.dataset.index); // currentMonth是正常月份所以要减一 309 | const inputValue = selectDay.getFullYear() + " - " + (selectDay.getMonth() + 1) + " - " + selectDay.getDate(); 310 | dateInput.value = inputValue; 311 | dateSelect.style.display = "none"; // 隐藏选择框 312 | },false) 313 | 314 | 315 | } 316 | let date = get42ArrayDate(); 317 | render(date); 318 | ``` 319 | --------------------------------------------------------------------------------
261 | < 262 | ${date.currentYear} - ${date.currentMonth} 263 | > 264 |
${item.showDate}