说来惭愧,好久没有更新博客了,大概过了两个多月吧,这两个多月当然并没有出去浪啊,还是在好好的学习代码的。前一段时间和后端合作了两个项目,一个是问卷平台的项目,就是这次将要总结的内容,还有一个是一个个人的博客。这两个项目也算是和后端的两次比较详细的合作,相比上次的微信小程序有了更大的提高。两个项目坐下来感觉技术提高了不少,但是由于赶进度的原因也欠下了很多技术债,寒假时候要好好的补一补了。不过正所谓业务驱动需求,需求驱动技术嘛。
两个项目下来,发现都用原生的代码写真的累,前一段时间每天基本都是代码,没怎么闲过,有很多重复的劳动,原生代码也写了不少,感觉可以进行学习一些框架了。
页面展示
界面做的还是很low的,不过也还勉强能看
技术细节
这一部分将整理一下在写页面的时候学到的一些新的技术。
首页
首页部分其实做的挺丑的,当时时间比较紧,所以并没有进行设计,直接就开始动手写了,这部分都是一些很常见的东西,但是有一块就是一个正反面的翻转效果感觉挺赞的,是从别的网站上看到然后学习了一下写出来的。
从技术上看实现方法就是采用了两个div
层,一个显示的是正面,另一个反面使用rotateY(-180deg)
来进行一个180度的翻转,显示成反面,然后使用z-index
来达到一个遮罩效果,然后就是hover
实现一个点击后的效果将正面设置为rotateY(180deg)
并将透明度opacity
设置成0,反面设置为rotateY(0deg)
透明度opacity
设置为1,来使转换更加的自然。不过需要注意的是要将position
设置为绝对定位或一些其他的方式,不然z-index不会生效。关键代码如下:
1 2 3 4 5 6 7 8 9 10
| <div class="developers"> <div class="renpicture"> <div class="img img1"></div> <div class="img-font"> <p>后端开发</p> </div> </div> <div class="developers-font LwolveJ-font">LwolveJ</div> </div>
|
1 2 3 4 5 6 7 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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| .developers { width: 33.3%; height: 500px; display: inline-block; vertical-align: top; text-align: center; position: relative; }
.renpicture { width: 250px; height: 250px; margin: 0 auto; position: relative; -webkit-perspective: 1000; }
.img , .img-font { position: absolute; margin: 0 auto; width: 250px; height: 250px; border-radius: 50%; box-shadow: 0 3px 5px #9E9E9E; -webkit-transition: all 0.5s; }
.img { z-index: 5; background-size: 250px 250px; }
.img1 { background-image: url("../img/lijie.jpg"); }
.img-font { font-size: 25px; background-color: #1799e5; -webkit-transform: rotateY(-180deg); opacity: 0; }
.img-font p { line-height: 250px; color: #FFFFFF; }
.renpicture:hover .img{ -webkit-transform:rotateY(180deg); z-index: 5; opacity: 0; }
.renpicture:hover .img-font { -webkit-transform: rotateY(0deg); z-index: 10; opacity: 1; }
|
生成问卷页面
这一个页面所涉及到的代码最多,而且也是该项目的重点,需求主要就是右侧的窗口能生成标题、单选题、多选题、简短回答这几个问卷常见的题型,而且右边所添加的选项能即时在右侧展示,并且能删除选项,然后点击生成问卷会在左侧显示出相应的效果。初次之外还有个比较人性化的细节,即右侧的操作框会随着左侧题目的位置而变动。效果大概就是这么多,那么就是一些技术的实现了。
首先是HTML结构,右侧操作框的HTML结构每种题型都写成一块HTML,然后都使用display:none
隐藏,然后默认将标题显示为第一个,然后通过js获取下拉框选择的题型来显示相应的格式。然后是CSS部分,这一部分都是一些之前就用过的,所以此处不再整理。本部分将着重整理js部分的代码。
表单切换功能:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var inputTypeSelectBtn = g('input-type-select');
var selectTypes = { "title": g("title-box"), "radio": g('radio-box'), "checkbox": g('checkbox-box'), "text": g('text-box') }
var getSelectContent = function(select) { var index = select.selectedIndex; return select.options[index].value; }
var selectValue = getSelectContent(inputTypeSelectBtn);
var changeType = function(type) { for(var i in selectTypes){ selectTypes[i].style.display = 'none'; } selectTypes[type].style.display = "block"; }
inputTypeSelectBtn.onchange = function() { selectValue = getSelectContent(inputTypeSelectBtn); changeType(selectValue); }
|
该部分思路是当用户点击题型切换时出发onchange
然后getSelectContent函数获取改变的值,并返回给selectValue值,然后当作参数传给changeType函数来控制相应表单的显示与隐藏,这里将四种题型的节点都写在了selectTypes对象里面,也同时方便了后面其他功能的实现。
选项的添加与删除功能
首先是绑定事件,分别是鼠标点击事件和键盘回车事件
1 2 3 4 5 6 7 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
| var addOptionsBtns = { 'radio': selectTypes['radio'].getElementsByClassName('add-btn')[0], 'checkbox': selectTypes['checkbox'].getElementsByClassName('add-btn')[0] }
var addOptionsInputs = { 'radio': selectTypes['radio'].getElementsByClassName('add-option')[0], 'checkbox': selectTypes['checkbox'].getElementsByClassName('add-option')[0] }
for(var key in addOptionsBtns) { (function(e) { addOptionsBtns[e].onclick = function() { var value = selectTypes[e].getElementsByClassName('add-option')[0].value; if(value === "") { return false; } addOption(selectTypes[e], e, value); selectTypes[e].getElementsByClassName('add-option')[0].value = ''; } })(key) }
for (var key in addOptionsInputs) { (function(e) { addOptionsInputs[e].addEventListener('keyup', function() { if (event.keyCode === 13) { addOptionsBtns[e].click(); } }); })(key) }
|
此处for循环里面的function事件采用了(function(e){}(key))
来调用,key即function的参数,当js运行到此处的时候将直接调用,里面的两个addOptionsBtns和addOptionsInputs对象分别对应button按钮的点击事件和input输入框的键盘事件,键盘事件实现的逻辑就是调用了点击事件addOptionsBtns[e].click();
。
然后是添加选项和删除选项功能
1 2 3 4 5 6 7 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
|
var Option = function(type, value, deleteBtn, selfElement) { this.type = type; this.value = value; this.deleteBtn = deleteBtn; this.deleteBtn.self = this; this.selfElement = selfElement; this.selfElement.self = this; }
var options = []; var addOption = function(selectTypes, key, value) {
var type = key; var optionsWrap = selectTypes.getElementsByClassName('options-wrap')[0]; var optionWrap = document.createElement('div'); optionWrap.className = 'option-wrap'; optionWrap.innerHTML = '<div class="delete-mask">删除</div> <span class="option-set">' + value + '</span>'; optionsWrap.appendChild(optionWrap); var deleteBtn = optionWrap.getElementsByClassName('delete-mask')[0]; var selfElement = optionWrap; var option = new Option(type, value, deleteBtn, selfElement); options.push(option); deleteBtn.addEventListener('click', function() { deleteOption(deleteBtn); }); }
var deleteOption = function(btn) { for(var i = 0; i < options.length; i++) { if(options[i] === btn.self) { options.splice(i ,1); break; } } btn.self.selfElement.parentNode.removeChild(btn.self.selfElement); delete btn.self; }
|
这一部分先是定义了一个Option()
函数,并在addOption()
中new了一个option
对象出来,然后使用push
方法依次存入开始定义的options
数组里面,此处的new是一个我目前还不完全理解的用法,以及js原型链,这一个项目整理完之后将整理一片关于new和js原型链的文章。
右侧框移动功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| var formSetBox = g('content-right'); var formBox = g('content-body'); var moveSetBox = function() { var questionElements = formBox.getElementsByClassName('question-wrap'); var topPosition = formBox.offsetHeight - questionElements[questionElements.length - 1].offsetHeight; startMove(formSetBox, topPosition, 10); }
var timer = null; var positionTop = 0; startMove = function(element, target, interval) {
clearInterval(timer); timer = setInterval(function() { var speed = (target - element.offsetTop) / 10; speed = speed > 0 ? Math.ceil(speed) : 0; if (element.offsetTop >= target) {
clearInterval(timer); } else { positionTop = positionTop + speed; element.style.top = positionTop + 'px'; } }, interval) }
|
moveSetBox()
函数用来计算目标位置即target,startMove()
函数是核心功能,target-element.offsetTop是计算目标位置和右侧框到顶部的距离只差。然后,此处需要一些数学的计算,计算出speed速度,interval参数是毫秒单位,即每10毫秒的speed,在该函数中即每10毫秒移动speed值的px,可知随着越接近位置,speed越来越小,但是由于ceil()
函数的存在,speed不会小于0。
HTMLElement.offsetHeight 是一个只读属性,它返回该元素的像素高度,高度包含该元素的垂直内边距和边框,且是一个整数。
HTMLElement.offsetTop 为只读属性,它返回当前元素相对于其 offsetParent 元素的顶部的距离。
问卷填写页面
这部分页面内容较为简单,有一处值得整理的就是页面间传参的一个功能,页面间传参就是说在问卷选择页面选择一个问卷之后会有使用window.location.href
来指定url,然后根据参数的不同来获得问卷内容,并渲染出相应的页面。这里是通过地址传值,并使用函数解析地址,获取到参数,然后GET请求获取到问卷内容。
1 2 3 4 5 6 7
| var parseURL = function(url) { var url = url.split("?")[1]; var res = url.split("=")[1]; console.log(res); return res; }
|
上面代码即获取参数的代码,res即参数。
问卷结果展示页面
该部分通过ajax获取相应选择人数数据,然后乘上一定比值,来使图标更加清晰,然后使用js渲染出来即可,建议可以先在html和对应css写上假内容,然后再来写js,之后将html部分注释掉即可。
后记
这次项目算得上真正意义的第一个和后端合作开发的项目,之前的微信小程序更多依靠着封装的API勉强完成,很多地方的原理都不太懂。这次通过完全的原生HTML+CSS+JS开发的项目,虽然代码较多,使用很多框架之类的可以有效的简化代码,但是基础还是很重要的,所以这次项目很有必要。