Angularjs 实现移动端在线测评效果(推荐)


Posted in Javascript onApril 05, 2017

注:此文所用的angular版本为 1.6

一、运行效果图

Angularjs 实现移动端在线测评效果(推荐)

二、需求

1. 点击选项时,背景变为黄色(即选中状态),并且自动切换到下一题

2. 切换到下一题时,顶部进度随之改变

3. 选中时要把对应的分值记录下来(因为要根据分值算出最后的测评结果)

4. 通过向右滑动可以查看前面做过的题目

5. 当前题目没选,无法切换到下一题

6. 当选中最后一道题目时,切换到测评结果页

三、具体实现

 题目json数据,总共10道题,这里为了节省篇幅,就只贴出3道了。 (Score是分数, OrderNo是答案序号)

{ "Questions":
 [
 { 
  "Question":"您的年龄范围:",
  "AnswerList":[
   {"Text":"30岁以下","Score":5,"OrderNo":0},
   {"Text":"30-39岁","Score":4,"OrderNo":1},
   {"Text":"40-49岁","Score":3,"OrderNo":2},
   {"Text":"50-59岁","Score":2,"OrderNo":3},
   {"Text":"60岁以上","Score":1,"OrderNo":4}]
 },
 { 
  "Question":"您的婚姻状况为:",
  "AnswerList":[
  {"Text":"未婚","Score":5,"OrderNo":1},
  {"Text":"已婚","Score":4,"OrderNo":2},
  {"Text":"单身有婚史","Score":3,"OrderNo":3},
  {"Text":"丧偶","Score":2,"OrderNo":4},
  {"Text":"不详","Score":1,"OrderNo":5}]
 },
 { 
  "Question":"您的收入需要用来供养其他人(如父母或子女)吗?",
  "AnswerList":[
  {"Text":"不需供养其他人","Score":5,"OrderNo":1},
  {"Text":"供养1人","Score":4,"OrderNo":2},
  {"Text":"供养2人","Score":3,"OrderNo":3},
  {"Text":"供养3人","Score":2,"OrderNo":4},
  {"Text":"供养4人或以上","Score":1,"OrderNo":5}]
 }
 ]
}

Html代码

<div class="wrapper" ng-controller="RiskTestController as vm">
 <div class="process-box">
 <ul>
  <li class="page-icon"><span class="icon icon-txt">1</span></li>
  <li class="page-icon"><span class="icon icon-txt">2</span></li>
  <li class="page-icon"><span class="icon icon-txt">3</span></li>
  <li class="page-icon"><span class="icon icon-txt">4</span></li>
  <li class="page-icon"><span class="icon icon-txt">5</span></li>
  <li class="page-icon"><span class="icon icon-txt">6</span></li>
  <li class="page-icon"><span class="icon icon-txt">7</span></li>
  <li class="page-icon"><span class="icon icon-txt">8</span></li>
  <li class="page-icon"><span class="icon icon-txt">9</span></li>
  <li class="page-icon"><span class="icon icon-txt">10</span></li>
 </ul>
 <div class="page-info">
  已完成 {{vm.count}}/10
 </div>
 </div>
 <ul class="list-box" id="listBox">
 <li class="list-item" ng-repeat="question in vm.questionList track by $index" ng-class="{'first-li': $index == 0}">
  <div class="question-box">
  <div class="question">{{$index + 1}}. {{question.Question}}</div>
  <ul class="answer">
   <li class="answer-item" 
   ng-repeat="answer in question.AnswerList track by $index" 
   ng-click="vm.OnClickAnswer(answer, $parent.$index)"
   ng-class="{'selected': answer.Selected}">
   {{vm.letter[$index]}}. {{answer.Text}}
   </li>
  </ul>
  </div>
 </li>
 </ul>
 <div ng-show="vm.showResult">
 <span>{{vm.point}}</span>
 </div>
 </div>

核心CSS样式代码

.wrapper{
  width: 100%;
  height: 100%;
  position: relative;
  overflow: hidden;
 }
 .process-box{
  width: 17.25rem;
  height: 2.5rem;
  line-height: 2.5rem;
  background-color: #FFF;
  margin: 1.5rem auto;
  border-radius: 0.2rem;
 }
 .page-icon{
  float: left;
  font-size: 0.4rem;
  color: #FFE7C9;
  width: 1.32rem;
  text-align: center;
 }
 .page-info{
  font-size: 0.65rem;
  color: #F3A84D;
 }
 .question-box{
  width: 17.25rem;
  background-color: #FFF;
  margin-left: 0.75rem;
  border-radius: 0.2rem;
 }
 .question{
  font-size: 0.8rem;
  color: #43689F;
  padding: 1.1rem 0 0.8rem 0.75rem;
 }
 .answer-item{
  font-size: 0.75rem;
  color: #80A1D0;
  border-top: 1px solid #EEE;
  padding: 1.1rem 0 1.1rem 1.0rem;
 }
 .icon-txt{
  background-color: orange;
  border-radius: 0.5rem;
  display: block;
  width: 0.8rem;
  height: 0.8rem;
  line-height: 0.8rem;
  margin: 0.95rem auto;
 }
 .icon-txt-active{
  background-color: #FFE7C9;
  border-radius: 0.3rem;
  display: block;
  width: 0.3rem;
  height: 0.3rem;
  line-height: 2.0rem;
  color: #FFF;
  margin: 1.25rem auto;
 }
 .list-item {
  width: 100%;
  position: absolute;
  transform: translate3d(100%,0,0);
  transition: transform 0.5s;
 }
 .first-li {
  transform: translate3d(0,0,0);
 }
 .selected {
  background-color: orange;
 }

控制器代码(Controller)

(function (agr) {
 //模块 - app
 var app = agr.module('app', []);
 //控制器 - 风险测评
 app.controller('RiskTestController', ['$scope', '$http', RiskTestController]);
 function RiskTestController($scope, $http) {
 var vm = this;
 vm.letter = ['A', 'B', 'C', 'D', 'E']; //答案编号
 vm.questionList = []; //题目
 vm.point = 0;  //得分
 vm.showResult = false; //是否显示结果页
 //加载数据 
 $http({
  method: 'GET',
  url: '/Service/RiskTest',
 }).then(function (resp) {  
  vm.questionList = resp.data.Questions;
 }, function (resp) {
  console.log("ERROR", resp);
 });
 var lis = document.querySelectorAll(".list-item"), //题目列表
  count = 0, //做了多少道题
  index = 0, //当前第几题
  BIG = 9; //最大索引值,因为总共10道题,所以是9(常量)
 //选择答案
 vm.OnClickAnswer = function (answer, $parentIndex) {

  var icons = document.querySelectorAll(".icon"),
  curr = $parentIndex; //当前题目索引
  next = $parentIndex + 1; //下一题索引
  nextQuestion = vm.questionList[next]; //下一道题
  //当前问题的答案列表
  var answerList = vm.questionList[$parentIndex].AnswerList;
  //为每个答案对象添加属性 Selected, 默认值为false
  for (var i = 0, len = answerList.length; i < len; i++) {
  answerList[i].Selected = false;
  }
  //将选中的答案设置为true (从而应用样式.selected 将背景色设置为黄色)
  answer.Selected = true;
  //判断是否为最后一道题
  if ($parentIndex < BIG) { //不是最后一题
  //改变顶部进度样式
  icons[curr].classList.remove("icon-txt");
  icons[curr].classList.add("icon-txt-active");
  //切换到下一题
  lis[curr].style.webkitTransform = 'Translate3d(-100%,0,0)';
  nextQuestion && (lis[next].style.webkitTransform = 'Translate3d(0,0,0)');
  } else { //是最后一题
  //改变顶部进度样式
  icons[curr].classList.remove("icon-txt");
  icons[curr].classList.add("icon-txt-active");
  //计算分数
  vm.point = CalcPoint();
  //显示测评结果
  vm.showResult = true;
  }
  //做了多少题
  count = CalcCount();

  //因为选中答案会自动切换到下一题,所以索引更新为next
  index = next;  
 }
 //计算分数
 var CalcPoint = function () {
  var point = 0;
  for (var i = 0, lenq = vm.questionList.length; i < lenq; i++) {
  for (var k = 0, lena = vm.questionList[i].AnswerList.length; k < lena; k++) {
   if (vm.questionList[i].AnswerList[k].Selected) {
   point += vm.questionList[i].AnswerList[k].Score;
   }
  }
  }
  return point;
 }
 //计算当前做了多少道题
 var CalcCount = function(){
  var count = 0;
  for (var i = 0, lenq = vm.questionList.length; i < lenq; i++) {
  for (var k = 0, lena = vm.questionList[i].AnswerList.length; k < lena; k++) {
   if (vm.questionList[i].AnswerList[k].Selected) {
   count++;
   }
  }
  }
  return count;
 }
 /** 触屏滑动效果处理 == 开始 == **/
 var offsetX = 0, //手指滑动偏移量
  startX,  //滑动开始时的X轴坐标点
  startTime; //手指滑动开始时间
 //触屏开始
 var startHandler = function (evt) {
  //每次触屏时将偏移量重置为0
  offsetX = 0;
  //记录X坐标
  startX = evt.touches[0].pageX;
  //取得时间戳
  startTime = new Date() * 1;
 };
 //触屏滑动
 var moveHandler = function (evt) {
  //阻止默认事件
  evt.preventDefault();
  //记录手指滑动的偏移量
  offsetX = evt.touches[0].pageX - startX;
  var curr = index,
  prev = index - 1,
  next = index + 1,
  prevQuestion = vm.questionList[prev],
  nextQuestion = vm.questionList[next],
  width = window.innerWidth; 
  //手指滑动时题卡跟着手指滑动(向右滑:[偏移量大于0,即正数,并且不是第一道题])
  if (offsetX > 0 && index > 0) {
  lis[curr].style.webkitTransform = 'Translate3d(' + offsetX + 'px, 0, 0)';
  prevQuestion && (lis[prev].style.webkitTransform = 'Translate3d(' + (offsetX - width) + 'px, 0, 0)');
  }
  //手指滑动时题卡跟着手指滑动(向左滑:[偏移量小于0,即负数,并且不是最后一题])
  if (offsetX < 0 && index < count) {
  lis[curr].style.webkitTransform = 'Translate3d(' + offsetX + 'px, 0, 0)';
  nextQuestion && (lis[next].style.webkitTransform = 'Translate3d(' + (offsetX + width) + 'px, 0, 0)');
  }
 };
 //触屏结束
 var endHandler = function (evt) {
  var boundary = window.innerWidth / 5, //当手指滑动的偏移量为屏幕的5分之一时才进行切换
  quickBoundary = 60,   //当手指快速滑动时,偏移量为60即可 
  endTime = new Date() * 1;   //获取结束时间戳
  //判断是否为快速滑动
  if (endTime - startTime > 1000) {
  //判断是向左滑还是向右滑
  if (offsetX > 0) {
   //判断是否达到切换偏移量
   if (offsetX >= boundary) {
   MoveToRight();
   } else {
   ResetMoveRight();
   }
  } else{
   if (offsetX < -boundary) {
   MoveToLeft();
   } else {
   ResetMoveLeft();
   }
  }
  } else {
  if (offsetX > 0) {
   if (offsetX >= quickBoundary) {
   MoveToRight();
   } else {
   ResetMoveRight();
   }
  } else {
   if (offsetX < -quickBoundary) {
   MoveToLeft();
   } else {
   ResetMoveLeft();
   }
  }
  }
 };
 //向右滑动事件
 var MoveToRight = function () {
  var curr = index,
  prev = index -1,
  prevQuestion = vm.questionList[prev];
  if (curr > 0) {
  lis[curr].style.webkitTransform = 'Translate3d(100%, 0, 0)';
  prevQuestion && (lis[prev].style.webkitTransform = 'Translate3d(0, 0, 0)');
  index--;
  }  
 }
 //右滑重置(当滑动距离没达到切换偏移量时,题卡回到原点)
 var ResetMoveRight = function () {
  var curr = index,
  prev = index -1,
  prevQuestion = vm.questionList[prev];
  lis[curr].style.webkitTransform = 'Translate3d(0, 0, 0)';
  prevQuestion && (lis[prev].style.webkitTransform = 'Translate3d(-100%, 0, 0)');
 }
 //向左滑动事件
 var MoveToLeft = function () {
  var curr = index,
  next = index + 1,
  nextQuestion = vm.questionList[next];
  if (curr < count) {
  lis[curr].style.webkitTransform = 'Translate3d(-100%, 0, 0)';
  nextQuestion && (lis[next].style.webkitTransform = 'Translate3d(0, 0, 0)');
  index++;
  } 
 }
 //左滑重置(当滑动距离没达到切换偏移量时,题卡回到原点)
 var ResetMoveLeft = function () {
  var curr = index,
  next = index + 1,
  nextQuestion = vm.questionList[next];
  lis[curr].style.webkitTransform = 'Translate3d(0, 0, 0)';
  nextQuestion && (lis[next].style.webkitTransform = 'Translate3d(100%, 0, 0)');
 }
 //监听滑动事件 
 var outer = document.getElementById("listBox");
 outer.addEventListener('touchstart', startHandler);
 outer.addEventListener('touchmove', moveHandler);
 outer.addEventListener('touchend', endHandler);
 

 /** 触屏滑动效果处理 == 结束 == **/
 }
})(angular);

Javascript 相关文章推荐
深入分析js中的constructor和prototype
Apr 07 Javascript
JSONP 跨域共享信息
Aug 16 Javascript
js中的数组Array定义与sort方法使用示例
Aug 29 Javascript
JS控件ASP.NET的treeview控件全选或者取消(示例代码)
Dec 16 Javascript
javascript异步编程的4种方法
Feb 19 Javascript
JavaScript搜索字符串并将搜索结果返回到字符串的方法
Apr 06 Javascript
js禁止浏览器页面后退功能的实例(推荐)
Sep 01 Javascript
jquery实现左右轮播图效果
Sep 28 jQuery
vue脚手架中配置Sass的方法
Jan 04 Javascript
node.js部署之启动后台运行forever的方法
May 23 Javascript
基于JS实现父组件的请求服务过程解析
Oct 14 Javascript
vue使用require.context实现动态注册路由
Dec 25 Vue.js
微信小程序中的onLoad详解及简单实例
Apr 05 #Javascript
微信小程序 页面跳转如何实现传值
Apr 05 #Javascript
微信小程序 数据遍历的实现
Apr 05 #Javascript
微信小程序 图片绝对定位(背景图片)
Apr 05 #Javascript
JS实现复选框的全选和批量删除功能
Apr 05 #Javascript
Ajax验证用户名或昵称是否已被注册
Apr 05 #Javascript
JS二叉树的简单实现方法示例
Apr 05 #Javascript
You might like
Smarty模板快速入门
2007/01/04 PHP
ajax+php打造进度条代码[readyState各状态说明]
2010/04/12 PHP
PHP使用JSON和将json还原成数组
2015/02/12 PHP
ThinkPHP框架实现的邮箱激活功能示例
2018/06/15 PHP
laravel-admin解决表单select联动时,编辑默认没选上的问题
2019/09/30 PHP
图片按比例缩放函数
2006/06/26 Javascript
基于jquery的图片懒加载js
2010/06/30 Javascript
js选取多个或单个元素的实现代码(用class)
2012/08/22 Javascript
js解析与序列化json数据(二)序列化探讨
2013/02/01 Javascript
JS调用CS里的带参方法实例
2013/08/01 Javascript
用jquery的方法制作一个简单的导航栏
2014/06/23 Javascript
jQuery调用ajax请求的常见方法汇总
2015/03/24 Javascript
js实现浏览本地文件并显示扩展名的方法
2015/08/17 Javascript
IE6-IE9使用JSON、table.innerHTML所引发的问题
2015/12/22 Javascript
Node.js刷新session过期时间的实现方法推荐
2016/05/18 Javascript
利用bootstrapValidator验证UEditor
2016/09/14 Javascript
js实现砖头在页面拖拉效果
2020/11/20 Javascript
浅谈移动端之js touch事件 手势滑动事件
2016/11/07 Javascript
快速实现JS图片懒加载(可视区域加载)示例代码
2017/01/04 Javascript
使用JavaScript触发过渡效果的方法
2017/01/19 Javascript
Vue.js bootstrap前端实现分页和排序
2017/03/10 Javascript
快速解决Vue项目在IE浏览器中显示空白的问题
2018/09/04 Javascript
JavaScript实现选项卡效果的分析及步骤
2019/04/16 Javascript
JavaScript代码压缩工具UglifyJS和Google Closure Compiler的基本用法
2020/04/13 Javascript
Vue中使用JsonView来展示Json树的实例代码
2020/11/16 Javascript
Python编程修改MP3文件名称的方法
2017/04/19 Python
Python文件读写保存操作的示例代码
2018/09/14 Python
Python3.7 dataclass使用指南小结
2019/02/22 Python
numpy.random模块用法总结
2019/05/27 Python
TensorFlow实现自定义Op方式
2020/02/04 Python
Python基于opencv的简单图像轮廓形状识别(全网最简单最少代码)
2021/01/28 Python
python读取excel数据并且画图的实现示例
2021/02/08 Python
canvas探照灯效果的示例代码
2018/11/30 HTML / CSS
澳大利亚快时尚鞋类市场:Billini
2018/05/20 全球购物
大学毕业感言一句话
2014/02/06 职场文书
喷漆工的岗位职责
2014/03/17 职场文书