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 相关文章推荐
读JavaScript DOM编程艺术笔记
Nov 15 Javascript
setTimeout的延时为0时多个浏览器的区别
May 23 Javascript
修改file按钮的默认样式实现代码
Apr 23 Javascript
禁用页面部分JavaScript方法的具体实现
Jul 31 Javascript
函数四种调用模式以及其中的this指向
Jan 16 Javascript
微信浏览器禁止页面下拉查看网址实例详解
Jun 28 Javascript
原生JS实现隐藏显示图片 JS实现点击切换图片效果
Jan 27 Javascript
JavaScript实现页面中录音功能的方法
Jun 04 Javascript
Vue的路由及路由钩子函数的实现
Jul 02 Javascript
Vue开发中遇到的跨域问题及解决方法
Feb 11 Javascript
原生js+css实现tab切换功能
Sep 17 Javascript
vue实现同时设置多个倒计时
May 20 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
Mysql和网页显示乱码解决方法集锦
2008/03/27 PHP
php 文件上传后缀名与文件类型对照表(几乎涵盖所有文件)
2010/05/16 PHP
php的日期处理函数及uchome的function_coomon中日期处理函数的研究
2011/01/12 PHP
用 JSON 处理缓存
2007/04/27 Javascript
jQuery checkbox全选/取消全选实现代码
2009/11/14 Javascript
JavaScript高级程序设计 阅读笔记(七) ECMAScript中的语句
2012/02/27 Javascript
2014 年最热门的21款JavaScript框架推荐
2014/12/25 Javascript
JQuery.get提交页面不跳转的解决方法
2015/01/13 Javascript
JavaScript实现字符串与日期的互相转换及日期的格式化
2016/03/07 Javascript
谈谈PHP中相对路径的问题与绝对路径的使用
2016/08/16 Javascript
用js控件div的滚动条,让它在内容更新时自动滚到底部的实现方法
2016/10/27 Javascript
详解Vue整合axios的实例代码
2017/06/21 Javascript
AngularJS发送异步Get/Post请求方法
2018/08/13 Javascript
解决vue路由后界面没有变化,但是链接有的问题
2018/09/01 Javascript
在node中使用jwt签发与验证token的方法
2019/04/03 Javascript
详解element-ui中表单验证的三种方式
2019/09/18 Javascript
Python 含参构造函数实例详解
2017/05/25 Python
python3学习笔记之多进程分布式小例子
2018/02/13 Python
使用Python来开发微信功能
2018/06/13 Python
Python----数据预处理代码实例
2019/03/20 Python
python rsa实现数据加密和解密、签名加密和验签功能
2019/09/18 Python
VSCode中自动为Python文件添加头部注释
2019/11/14 Python
python根据完整路径获得盘名/路径名/文件名/文件扩展名的方法
2020/04/22 Python
python 使用递归的方式实现语义图片分割功能
2020/07/16 Python
用CSS3的box-reflect来制作倒影效果
2016/11/15 HTML / CSS
真正的英国宝藏:Mappin & Webb
2019/05/05 全球购物
为什么说Ruby是一种真正的面向对象程序设计语言
2012/10/30 面试题
城市规划毕业生求职信
2013/10/10 职场文书
《三亚落日》教学反思
2014/04/26 职场文书
安全技术说明书
2014/05/09 职场文书
爱我中华演讲稿
2014/05/20 职场文书
校园文明标语
2014/06/13 职场文书
离婚律师函范本
2015/05/27 职场文书
消防演习感想
2015/08/10 职场文书
使用logback实现按自己的需求打印日志到自定义的文件里
2021/08/30 Java/Android
Java中的Kotlin 内部类原理
2022/06/16 Java/Android