50 Projects 50 Days - Progress Steps 学习记录
50 Projects 50 Days不使用任何前端框架,适合初学者练手,巩固前端基础,在这里记录一下学习过程,尤其是一些细节上的问题。
项目地址
Progress Steps
展示效果
Progress Steps
实现思路
进度条和结点分开处理:
1. 步骤结点外观通过设置border的圆角和颜色得到;当点击按钮时根据计算将对应结点外边框颜色变更为蓝色。
2. 进度槽默认外观通过伪类before来实现(也可以单独再写一个HTML元素),以父盒子为初始定位,构造一个类似的横线的盒子,层级设置低一些,被结点所覆盖。
3. 进度条的外观则再定义一个和before伪类定位、层级、高度相同的盒子,而它的宽度默认为0,每次点击按钮时通过程序计算改变。
按钮触发的程序:
1. 用一个数字记录目前进度的位置。
2. 进度前的结点设置active类,进度后的移除active类。
3. 对进度条的样式宽度重新计算。
4. 根据当前位置,判断按钮是否还可用。
实现细节
HTML结构
在一个container
,主要拆分为进度条和按钮两个区域,因此整体结构划分为progress-container
和button
。
progress-container
除了几个结点外,再放入一个进度条即可。
button
可以再用一个div包起来,也可以直接并排放,本身是行内元素,也可以居中作处理。
button
和progress
设置了一些id,主要在script中使用。
<div class="container"><div class="progress-container"><div id="progress"></div><div class="node active">1</div><div class="node">2</div><div class="node">3</div><div class="node">4</div></div><button class="btn" id="prev" disabled>Prev</button><button class="btn" id="next">Next</button></div>
CSS样式
在全局定义两种经常用到的颜色,方便后面直接使用该CSS变量。
:root{--line-border-fill: #3498db;--line-border-empty: #383838;
}
container
和progress-container
任一元素设置宽度对本次案例效果没有影响。为了让button
居中显示,因此用到了text-align
属性。
.container{width: 350px;text-align: center;
}
根据MDN的说明,text-align
属性定义行内内容(例如文字)如何相对它的块父元素对齐。
progress-container
progress-container
设置flex布局,并为子元素绝对定位作为参照父元素设置为相对定位。
.progress-container{display: flex;justify-content: space-between;margin-bottom: 30px;position: relative;color: #fff;
}
进度槽可以用伪类元素实现,也可以再单独写一个div然后通过绝对定位来实现。相对来说,伪类减少了HTML元素的使用,也可以利用浏览器缓存。
另外要注意::before
需要定义content,即使是空字符串,否则是不会被浏览器创建。::before
和::after
伪元素的原始目的是在原始元素的主要内容之前和之后插入生成的内容。当没有要插入的内容时,创建一个仅插入任何内容的附加框是没有意义的。因此,none值指示浏览器不要为创建其他框而烦恼。
之后还需要定义长宽的尺寸,设置绝对定位,并从左开始上下居中显示,最后是设置层级靠下,被node元素所覆盖。
.progress-container::before{content:''; height: 4px;width:100%;background-color: var(--line-border-empty);position:absolute;left:0;top: 50%;transform: translateY(-50%);z-index: -1;
}
进度条的实现就与进度槽类似了。
#progress{height: 4px;width: 0;background-color: var(--line-border-fill);position:absolute;left:0;top: 50%;transform: translateY(-50%);z-index: -1;transition: 0.4s ease;
}
这里虽然z-index同样都是-1,但是before
元素在DOM流中元素靠前,而进度条靠后,所以进度条层级会更高,覆盖进度槽。这里可以试着把before
改为after
来验证这一原因,之后再把伪类的z-index改为-2会发现层级又会变下去了。
node元素只要做一个圆形的外观即可,注意将中间的文字设为居中显示。然后设计一个外边框,在active类中改变边框的颜色。
.node {width: 30px;height: 30px;border-radius: 50%;border:3px solid var(--line-border-empty) ;background-color: var(--line-border-empty);display: flex;align-items: center;justify-content: center;transition: 0.4s ease;
}.node.active{border-color: var(--line-border-fill);
}
按钮的外观很简单,设置padding将尺寸撑开,在disabled状态自定义颜色和交互方式。
.btn{border-width: 0;border-radius: 6px;background-color: var(--line-border-fill);color: #fff;font-size: 14px;margin: 5px;padding: 8px 30px;cursor: pointer;
}.btn:disabled{background-color: var(--line-border-empty);cursor: not-allowed;
}
JavaScript逻辑
需要用到的元素有两个按钮、进度条和步骤结点。同时全局维护一个变量记录当前进度的位置。
const prev = document.getElementById('prev');
const next = document.getElementById('next');
const progress = document.getElementById('progress');
const nodes = document.querySelectorAll('.node');let currentActive = 0;
根据前面的思路,两个按钮触发的公共事件主要有三:
1. 根据当前位置,判断按钮是否还可用。
2. 进度前的结点设置active类,进度后的移除active类。
3. 对进度条的样式宽度重新计算。
const update = () => {if(currentActive <= 0){prev.setAttribute('disabled', true);}else if(currentActive >= nodes.length-1){next.setAttribute('disabled', true);}else{prev.removeAttribute('disabled');next.removeAttribute('disabled');}nodes.forEach((node, index) => {if(currentActive >= index){node.classList.add('active');return;}node.classList.remove('active');});progress.style.width = currentActive/(nodes.length-1) * 100 + '%';
}
接着分别给两个按钮绑定点击事件,主要是控制全局变量的增加或减小,对于边界的判定因为之后设置disabled所以也可以不写,不过写上可以说是防抖设计了。
prev.addEventListener('click', ()=>{currentActive--;if(currentActive < 0){currentActive = 0;}update();
});next.addEventListener('click', ()=>{currentActive++;if(currentActive >= nodes.length){currentActive = nodes.length-1;}update();
});
总结
- 进度条可以使用绝对定位,宽度100%+低层级来实现
text-align
不仅作用于文本,对行内元素均有效before
伪类可以减少HTML元素数量,但是需要定义content
- z-index相同的情况下,DOM流靠后的元素会覆盖掉前面的元素