答题卡效果
在前端开发中,我们大量使用开源很多UI框架和js框架,让我们使用的越好,做项目越快,但是同时让我们也对最基本的css属性和js最基本和最底层的api都遗忘,所以我们通过自己封装插件和组件,让我们更容易拾起最基本的知识点;以下我会从3个部分总结这个过程:1.原生js和jquery实现基本tab效果,2.使用面向对象js和jquery常用封装方法,3.通过vue封装和优化AnswerSheet组件
核心js封装
/**
* 插件作用:
* 1. 切换效果
* 2. 封装初始化模板,前端不需要构建dom结构
* 3. 对数据的处理,传入数据,修改数据状态,返回更新后数据
* 4. 监测用户正在操作当前数据状态
*/
(function(window) {
function AnswerSheet(options,callback) {
var opts = Object.assign({}, AnswerSheet.defaultOptions, options);
this.callback = callback;
this.opts = opts;
this.insertDom = opts.insertDom;
this.questions = opts.questions;
this.activeFiled = opts.activeFiled;
this.currentIndex = 0;//记录当前题目的索引
this.cardConts = null;//获取所有卡片node
this.btnNodes = null;//下一个按钮node
this.answerLables = null;//当前卡片的答案
this.activedQuestionNode = null;//记录激活状态答案node
this.init();
}
AnswerSheet.prototype = {
constructor: AnswerSheet,
init: function() {
this.initData(this.questions);
this.initTemplate(this.questions);
},
//初始化模板,
initTemplate: function(data) {
let strHtml = ` <div id="answer" class="card_wrap">`;
data.forEach((item, index) => {
strHtml += `
<div class="card_cont card${index}" style="z-index:${data.length - index}">
<div class="card">
<p class="card_top">${index + 1}. ${item.question}</p>
<div class="card_bottom">
${item.answerList.length > 0 && this.renderChild(item.answerList)}
<div class="answer-bottom">
${index > 0 ? '<span class="prev" >上一题</span>' : ''}
<span class="answer-currentNum">${index + 1}</span>/ <span class="answer-totalNum">${this.questions.length}</span>
</div>
</div>
</div>
</div>
`
})
strHtml += `</div>`
this.insertDom.appendChild(this.strToNode(strHtml));
this.cardConts = document.querySelectorAll(".card_cont");
this.btnNodes = document.querySelectorAll(".prev");
this.answerLables =document.querySelectorAll(".answer-item");
//给答案绑定事件
this.bindEvent(this.answerLables,"click", this.nextHandler);
//给上一个按钮绑定事件
this.bindEvent(this.btnNodes, "click", this.prevHandler);
},
renderChild: function(answerList) {
let strChild = ` <div class="answer-wrapper">`;
answerList.forEach(item => {
strChild += `<div class="answer-item" >${item.lable}</div>`
})
strChild += `</div>`
return strChild;
},
//初始化数据
initData: function(data) {
data.forEach(item => {
item.answerList.forEach(child => {
child[this.activeFiled] = false;
})
});
},
//初始化card编号
initCardNum: function() {
},
//点击答案下一个卡片操作
nextHandler(e) {
//设置激活状态
this.setActivedClass(e.target)
//更新数据
this.updateData(e.target.textContent,this.currentIndex);
let restCount = this.cardConts.length - (this.currentIndex + 1);
if(restCount <= 0) {
this.callback && this.callback(this.questions,this.currentIndex);
return;
}
this.cardConts[this.currentIndex].classList.remove("card0");
this.cardConts[this.currentIndex].classList.add("cardn");
restCount >=1 && this.changeCardClass("next",1, this.cardConts[this.currentIndex]);
restCount >=2 && this.changeCardClass("next",2, this.cardConts[this.currentIndex]);
restCount >=3 && this.changeCardClass("next",3, this.cardConts[this.currentIndex]);
this.currentIndex += 1;
},
//点击上一个按钮卡片操作
prevHandler() {
let rest2 = this.currentIndex;
let rest = this.cardConts.length - (this.currentIndex + 1);
if(rest2 <= 0) {
alert("上面没题了")
return
}
//把当前变为cardn
this.cardConts[this.currentIndex].classList.remove("card0");
this.cardConts[this.currentIndex].classList.add("card1");
//把上一个变为card0
rest2 >=1 && this.changeCardClass("prev", -1, this.cardConts[this.currentIndex])
//把下一个变为card0
rest >=1 && this.changeCardClass("prev",1, this.cardConts[this.currentIndex]);
//把下下个变为card1
rest >=2 && this.changeCardClass("prev",2, this.cardConts[this.currentIndex]);
//把下下下个变为card2
// rest >=2 && changeCardClass("prev",3, cardConts[currentIndex]);
this.currentIndex -= 1;
},
//获取当前card的相邻的卡片,处理相邻卡片类名变化
changeCardClass(type, num, currentNode) {
let _temp = null;
switch (num) {
case -1:
_temp = currentNode.previousElementSibling;
break;
case 1:
_temp = currentNode.nextElementSibling;
break;
case 2:
_temp = currentNode.nextElementSibling.nextElementSibling;
break;
case 3:
_temp = currentNode.nextElementSibling.nextElementSibling.nextElementSibling;
break;
default:
break;
}
//区分上一个和下一个操作
if(type == "next") {
_temp.classList.remove("card" + num);
_temp.classList.add("card" + (num - 1));
}else {
if(num < 1) {
_temp.classList.remove("cardn");
_temp.classList.add("card0");
}else {
_temp.classList.remove("card" + num);
_temp.classList.add("card" + (num + 1));
}
}
},
//给选中答案设置激活样式
setActivedClass: function(node) {
//把兄弟node去除actice
let siblingNodes = node.parentNode.childNodes;
siblingNodes.forEach(item => {
item.classList.remove("active");
})
node.classList.add("active");
},
//修改数据状态
updateData: function(value, index) {
console.log(value, index);
let question = this.questions[index];
question.answerList.forEach(item => {
item[this.activeFiled] = false;
})
let answer = question.answerList.find(item => item.lable == value);
answer[this.activeFiled] = true;
console.log(this.questions)
},
//绑定事件
bindEvent: function(node, event, cb) {
if(node.length >= 0) {
Array.prototype.forEach.call(node, item => {
item.addEventListener(event, (e) => {
cb && cb.call(this,e);
}, false)
})
}else {
node.addEventListener(event, (e) => {
cb && cb.call(this,e);
}, false)
}
},
//字符串转换成 DOM对象
strToNode: function(strHtml) {
return new DOMParser().parseFromString(strHtml,'text/html').body.childNodes[0];
}
}
AnswerSheet.defaultOptions = {
insertDom: null,//设置要插入的节点
questions: [],//传入题目数据,结构是有要求的,层级感
activeFiled: "checked",//设置答案的激活状态字段
}
return window.AnswerSheet = AnswerSheet;
})(window)
使用
- data - 模拟数据源
var data = [
{
id: "001",
question: "这是第一个问题",
answerList: [
{
id:"001",
lable: "A",//这个是前端展示的字段,根据自己业务需求进行设置
value: 10// 这个是后台真实所处理的字段,根据自己业务需求进行设置
},{
id:"002",
lable: "B",
value: 20
},{
id:"003",
lable: "C",
value: 10
},{
id:"004",
lable: "D",
value: 20
}
]
},{
id: "002",
question: "这是第二个问题",
answerList: [
{
id:"001",
lable: "A",//这个是前端展示的字段,根据自己业务需求进行设置
value: 10// 这个是后台真实所处理的字段,根据自己业务需求进行设置
},{
id:"002",
lable: "B",
value: 20
},{
id:"003",
lable: "C",
value: 10
},{
id:"004",
lable: "D",
value: 20
}
]
},{
id: "003",
question: "这是第三个问题",
answerList: [
{
id:"001",
lable: "A",//这个是前端展示的字段,根据自己业务需求进行设置
value: 10// 这个是后台真实所处理的字段,根据自己业务需求进行设置
},{
id:"002",
lable: "B",
value: 20
},{
id:"003",
lable: "C",
value: 10
},{
id:"004",
lable: "D",
value: 20
}
]
},{
id: "004",
question: "这是第四个问题",
answerList: [
{
id:"001",
lable: "A",//这个是前端展示的字段,根据自己业务需求进行设置
value: 10// 这个是后台真实所处理的字段,根据自己业务需求进行设置
},{
id:"002",
lable: "B",
value: 20
},{
id:"003",
lable: "C",
value: 10
},{
id:"004",
lable: "D",
value: 20
}
]
},{
id: "005",
question: "这是第五个问题",
answerList: [
{
id:"001",
lable: "A",//这个是前端展示的字段,根据自己业务需求进行设置
value: 10// 这个是后台真实所处理的字段,根据自己业务需求进行设置
},{
id:"002",
lable: "B",
value: 20
},{
id:"003",
lable: "C",
value: 10
},{
id:"004",
lable: "D",
value: 20
}
]
}
]
- html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>答题卡</title>
<style>
* {
margin: 0;
padding: 0;
}
.wrapper {
padding-top: 34px;
padding-left: 12px;
padding-right: 12px;
padding-bottom: 30px;
background-color: #999;
}
.card_wrap {
width: 100%;
height: 500px;
position: relative;
overflow: hidden;
}
.card_cont {
width: 100%;
height: 452px;
box-sizing: border-box;
margin: 0 auto;
position: absolute;
border-radius: 10px;
/* background-color:rgb(59, 178, 115); */
background-color: #fff;
box-shadow: 1px 1px 2px #ddd;
position: absolute;
bottom: 0;
display: block;
transition: all 1s;
}
/* 表示在视图第一个卡片状态 */
.card0 {
transform: scale(1,1) translate(0, 0) !important;
}
/* 表示在视图第二个卡片状态 */
.card1 {
transform: scale(.85,.85) translate(0, -62px) !important;
}
/* 表示在视图第三个卡片状态 */
.card2 {
transform: scale(.72,.72) translate(0, -135px) !important;
}
.next {
color: #f00;
position: relative;
right: 0;
bottom:0;
}
.cardn {
transform: translate(0, -1000px);
}
/* 卡片样式 */
.card {
/* height: 3.75rem; */
/* line-height: 3.75rem; */
}
.card .card_top {
line-height: 60px;
text-indent: 40px;
color: #fff;
background-color:rgb(59, 178, 115);
}
.card .card_bottom {
background-color: #fff;
}
.card .card_bottom .answer-wrapper {
}
.answer-item {
color: rgb(59, 178, 115);
font-size: 14px;
line-height: 30px;
text-align: center;
border-radius: 20px;
border: 1px solid rgb(59, 178, 115);
width: 60%;
margin: 20px auto;
}
.answer-bottom {
color: rgb(59, 178, 115);
text-align: right;
margin-top: 20px;
width: 60%;
margin: 60px auto;
}
.prev {
float: left;
}
.active {
background-color: rgb(59, 178, 115);
color: #fff;
}
</style>
</head>
<body>
<div class="wrapper"></div>
</body>
<script src="./js/data.js"></script>
<script src="./js/answerSheet.js"></script>
<script>
new AnswerSheet({
insertDom: document.getElementsByClassName("wrapper")[0],
questions: data
}, function(data,index) {
alert("做完题了,可以进行自己的操作逻辑")
alert("提交数据"+ JSON.stringify(data));
})
</script>
</html>
效果
1619167053(1).jpg下次更新使用vue封装AnswerSheet组件
使用vue封装组的新功能:
- 根据题目种类变颜色
- 可以人工播放题目功能
万水千山总是情,点波关注行不行呦!!!
网友评论