近教学过程中需要实现form表单中的input文本框在用户点击按钮时自动生成新的文本框,在完成数据填写之后通过ajax批量将填写的信息存储到数据库中。每一行文本框对应数据库表中的记录。初始状态如下图:
文本框组合(行)
如上图,前端用户需要借助文本框批量提交数据,每一行有5个文本框,对应后台表中的一条记录。用户可填写数据,完成填写后点击添加,页面自动添加新的一行文本框。效果如下:
动态文本框行添加生成效果
上图给出了点击添加之后的新效果,整个文档HTML代码描述如下:
表单代码
代码描述如上,文本框以label标签为基础进行分组,每5个为一行,所有的input文本框具有相同的class名称。
针对待解决的问题,设计采用JavaScript DOM文档对象模型提供的方法,将表单看成结点,调用附加结点方法实现动态结点的添加。主要涉及使用到的方法函数如下:
(1) getElementById()方法
获取元素(结点)函数,通过使用HTML标记对应的ID标签值获取该元素,返回元素对象,可进一步执行元素的操作。
(2) createElement()方法
创建结点元素方法,该方法用于实现动态创建新结点元素,为下一步添加新结点奠定基础。
(3) setAttribute()方法
该方法主要用于实现结点属性的设置,函数需要提供两个参数,分别为属性名称与属性值。
(4) appendChild()方法
附加结点方法,该方法主要用于将新创建结点附加到指定结点的尾部。
(5) getElementsByClassName()方法
该方法主要用于返回HTML文档中具有相同Class名字的一组元素的集合,并可通过下标访问其中的每一个元素。
为将前端表单文本框组合与数据表中行对应,涉及采用插入数据类insetv实现对每一行文本框数据进行存储。该类设计描述如下:
行数据(记录)类
每一行使用实例化的insetv进行数据存储,针对多行的情况,设计采用数组对每一行进行存储。即数组的每一个元素都是insetv对象。最终实现数组按照数据行对填写信息进行存储。然后自定义两个按钮的onclick消息响应函数,实现存储数据与动态表格生成。其中addRow用于生成新的行,该函数实现代码描述如下:
添加文本框(行)函数
定义insert方法用于获取表单所有文本框填写的信息,并采用数组对获取的信息进行存储,数组每一个元素为insetv对象,对应每一条记录。Insert方法实现描述如下图所示:
插入数据操作函数
本例所有的文本框行都是JS编码实现,因此需要在window对象的onload实践中调用addRows方法实现初始化时生成第一行。实现代码如下:
初始化首行函数
为方便测试演示与展示,我们将获取的数据直接在本页面进行展示,实际测试过程描述如下动图所示:
动态实现效果展示
在插入操作过程我们使用console.log()对输入数据对应数组进行了控制台的输出。输出结果如下:
数据获取存储结果
本头条号长期关注编程资讯分享;编程课程、素材、代码分享及编程培训。如果您对以上方面有兴趣或代码错误、建议与意见,可以联系作者,共同探讨。期待大家关注!如需案例完整代码请关注并私信,该案例相关文章链接如下:
前端开发-拼图游戏(N数码问题)A*算法智能求解效果展示
前端设计-JavaScript美女拼图游戏开发实例
前端设计-教你如何快速绘制HTML5动画
在本文中,我们将与大家分享 10个最佳的极简 CSS 框架,它们能够为你提供建站必备的组件,帮助你节省时间。
希望对大家有所帮助!
一、Spectre(一个轻量级、响应式的现代 CSS 框架,用于快速建站和扩展程序的开发。它通过最佳编码实践和一致性的设计语言,为排版与元素、基于 Flexbox 的响应布局系统、CSS 组件提供了基本样式。)
二、Milligram(提供了极简样式设置,便于你快速、简洁的开启建站之旅。虽然它不是一个 UI 框架,但它的设计理念却是以提供优秀的性能、高效的开发效率以及最少的属性重置而构建的。同时,它也是轻量的)
三、Mobi(是一个轻量级、可扩展、移动优先的 CSS 框架。它专注于细节,对于内容丰富的网页能够提供优质的用户体验。虽然,它专注移动端,但桌面客户端的体验也是很棒的。)
四、Mini(作为一个 Gzip 压缩后不到 7KB 大小的极简框架,它具备响应式、易用性和定制性等特性,旨在为你提供尽可能多的功能。由于它是轻量框架,不仅让你创建的网站和应用程序具备更快的加载速度,而且它所提供的组件可以满足基本的开发要求。)
五、 Siimple(是一个助你打造扁平化网站风格的轻量、响应式的开源框架。它内置了 SASS / SCSS,为你的网页设计提供了简洁的开始。)
六、Base(一个稳固的响应式 HTML 与 CSS 框架。)
七、Scooter(专注为 Dropbox 提供基础样式、CSS 组件以及快速静态原型的 SCSS 框架。)
八、Responsive(一个功能强大、对开发人员友好的,用于构建响应式网站的轻量级前端框架。)
九、 拼图 Pintuer (国内优秀的HTML、CSS、JS跨屏响应式开源前端框架,使用最新浏览器技术,为快速的前端开发提供一系统的文本、图标、媒体、表格、表单、按钮、菜单、网格系统等样式工具包,占用资源小,使用拼图可以快速构建简洁、优雅而且自动适应手机、平板、桌面电脑等设备的前端界面,让前端开发像玩游戏一下快乐而轻松。)
十、 BluCSS (是一个简便易记的CSS框架。可轻松应用在项目中。)
相信大家都会自己心中最喜欢的工具,如果本文没有列举出来,欢迎大家在评论区留下自己心目中最喜欢、最有价值的工具~~
切图 qietu(.com)
学lufylegend.js之日,我用lufylegend.js开发了第一个HTML5小游戏——拼图游戏,还写了篇博文来炫耀一下:HTML5小游戏《智力大拼图》发布,挑战你的思维风暴。不过当时初学游戏开发,经验浅薄,所以没有好好专研游戏里的算法和代码的缺陷,导致游戏出现了很多bug,甚至拼图打乱后很可能无法复原。最近经常有朋友问起这个游戏,希望我能把代码里的bug改一下方便初学者学习,顺便我也打算测试一下自己写这种小游戏的速度,所以就抽出了一些时间将这个游戏从头到尾重新写了一遍,计算了一下用时,从准备、修改素材到最后完成游戏,一共用了大约2h的时间。
以下是游戏地址:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
这是我的游戏记录,欢迎各位挑战:
接下来就来讲讲如何开发完成这款游戏的。(按“编年体”)
准备lufylegend游戏引擎,大家可以去官方网站下载:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
引擎文档地址:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
可以说,如果没有强大的lufylegend引擎,这种html5小游戏用原生canvas制作,少说要一天呢。
准备素材(10min) + 修改素材(20min)。由于在下实在手残,不善于P图,修改图片用了大约20min,囧……
开发开始界面。游戏不能没有开始界面所以我们首先实现这部分代码。在此之前是index.html里的代码,代码如下:
<!DOCTYPE html>
<html>
<head>
<title>Puzzle</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
<script type="text/javascript" src="./lib/lufylegend-1.10.1.simple.min.js"></script>
<script type="text/javascript" src="./js/Main.js"></script>
</head>
<body style="margin: 0px; font-size: 0px; background: #F2F2F2;">
<div id="mygame"></div>
</body>
</html>
主要是引入一些js文件,不多说。然后准备一个Main.js文件,在这个文件里添加初始化界面和加载资源的代码:
/** 初始化游戏 */
LInit(60, "mygame", 390, 580, main);
var imgBmpd;
/** 游戏层 */
var stageLayer, gameLayer, overLayer;
/** 拼图块列表 */
var blockList;
/** 是否游戏结束 */
var isGameOver;
/** 用时 */
var startTime, time, timeTxt;
/** 步数 */
var steps, stepsTxt;
function main () {
/** 全屏设置 */
if (LGlobal.mobile) {
LGlobal.stageScale=LStageScaleMode.SHOW_ALL;
}
LGlobal.screen(LGlobal.FULL_SCREEN);
/** 添加加载提示 */
var loadingHint=new LTextField();
loadingHint.text="资源加载中……";
loadingHint.size=20;
loadingHint.x=(LGlobal.width - loadingHint.getWidth()) / 2;
loadingHint.y=(LGlobal.height - loadingHint.getHeight()) / 2;
addChild(loadingHint);
/** 加载图片 */
LLoadManage.load(
[
{path : "./js/Block.js"},
{name : "img", path : "./images/img.jpg"}
],
null,
function (result) {
/** 移除加载提示 */
loadingHint.remove();
/** 保存位图数据,方便后续使用 */
imgBmpd=new LBitmapData(result["img"]);
gameInit();
}
);
}
function gameInit (e) {
/** 初始化舞台层 */
stageLayer=new LSprite();
stageLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EFEFEF");
addChild(stageLayer);
/** 初始化游戏层 */
gameLayer=new LSprite();
stageLayer.addChild(gameLayer);
/** 初始化最上层 */
overLayer=new LSprite();
stageLayer.addChild(overLayer);
/** 添加开始界面 */
addBeginningUI();
}
以上代码有详细注释,大家可以对照引擎文档和注释进行阅读。有些全局变量会在以后的代码中使用,大家可以先忽略。接下来是addBeginningUI函数里的代码,用于实现开始界面:
function addBeginningUI () {
var beginningLayer=new LSprite();
beginningLayer.graphics.drawRect(0, "", [0, 0, LGlobal.width, LGlobal.height], true, "#EDEDED");
stageLayer.addChild(beginningLayer);
/** 游戏标题 */
var title=new LTextField();
title.text="拼图游戏";
title.size=50;
title.weight="bold";
title.x=(LGlobal.width - title.getWidth()) / 2;
title.y=160;
title.color="#FFFFFF";
title.lineWidth=5;
title.lineColor="#000000";
title.stroke=true;
beginningLayer.addChild(title);
/** 开始游戏提示 */
var hint=new LTextField();
hint.text="- 点击屏幕开始游戏 -";
hint.size=25;
hint.x=(LGlobal.width - hint.getWidth()) / 2;
hint.y=370;
beginningLayer.addChild(hint);
/** 开始游戏 */
beginningLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
beginningLayer.remove();
startGame();
});
}
到此,运行代码,得到我们的开始界面:
看到这个画面,其实我自己都想吐槽一下实在是太“朴素”了,囧……
不过我这次图个制作速度,所以还望各位看官海量。
这40分钟的时间,是最关键时期,期间我们要完成整个游戏的主体部分。首先,我们需要用代码来实现以下过程:
初始化游戏界面数据(如游戏时间、所用步数)和显示一些UI部件(如图样)
|
-> 获取随机的拼图块位置
|
-> 显示打乱后的拼图块
我们将这些步骤做成一个个的函数方便我们统一调用:
function startGame () {
isGameOver=false;
/** 初始化时间和步数 */
startTime=(new Date()).getTime();
time=0;
steps=0;
/** 初始化拼图块列表 */
initBlockList();
/** 打乱拼图 */
getRandomBlockList();
/** 显示拼图 */
showBlock();
/** 显示缩略图 */
showThumbnail();
/** 显示时间 */
addTimeTxt();
/** 显示步数 */
addStepsTxt();
stageLayer.addEventListener(LEvent.ENTER_FRAME, onFrame);
}
函数一开始,我们把isGameOver变量设定为false代表游戏未结束,在后期的代码里,我们会看到这个变量的作用。接着我们初始化了用于表示时间和步数的time和steps这两个全局变量,另外初始化变量startTime的值用于后面计算游戏时间。
接下来,我们就要开始初始化拼图块了。见initBlockList里的代码:
function initBlockList () {
blockList=new Array();
for (var i=0; i < 9; i++) {
/** 根据序号计算拼图块图片显示位置 */
var y=(i / 3) >>> 0, x=i % 3;
blockList.push(new Block(i, x, y));
}
}
这里我们使用了一个Block类,这个类用于显示拼图块和储存拼图块的数据,并提供了一些方法来操控拼图块,下面是其构造器的代码:
function Block (index, x, y) {
LExtends(this, LSprite, []);
var bmpd=imgBmpd.clone();
bmpd.setProperties(x * 130, y * 130, 130, 130);
this.bmp=new LBitmap(bmpd);
this.addChild(this.bmp);
var border=new LShape();
border.graphics.drawRect(3, "#CCCCCC", [0, 0, 130, 130]);
this.addChild(border);
this.index=index;
this.addEventListener(LMouseEvent.MOUSE_UP, this.onClick);
}
Block类继承自LSprite,属于一个显示对象,所以我们在这个类中添加了一个位图对象用于显示拼图块对应的图片。除此之外,我们还为拼图块添加了一个边框,在显示时用于隔开周围的拼图块。Block类有一个index属性,代表拼图块在拼图块列表blockList中的正确位置。最后,我们为此类添加了一个鼠标按下事件,用于处理鼠标按下后移动图块操作。
接下来我们还要介绍这个类的一个方法setLocation:
Block.prototype.setLocation=function (x, y) {
this.locationX=x;
this.locationY=y;
this.x=x * 130;
this.y=y * 130;
};
这个方法用于设置拼图块对象的显示位置以及保存拼图块的“数组位置”。什么是“数组位置”呢?各位看官可以通过下面的图片加以了解:
可以看到,“数组位置”就类似于二维数组中的元素下标。储存这个位置的作用在于可以很方便地从blockList中获取到附近的其他拼图块。这个方法在我们显示拼图时有调用到,在显示拼图之前,我们得先打乱拼图,见如下代码:
function getRandomBlockList () {
/** 随机打乱拼图 */
blockList.sort(function () {
return 0.5 - Math.random();
});
/** 计算逆序和 */
var reverseAmount=0;
for (var i=0, l=blockList.length; i < l; i++) {
var currentBlock=blockList[i];
for (var j=i + 1; j < l; j++) {
var comparedBlock=blockList[j];
if (comparedBlock.index < currentBlock.index) {
reverseAmount++;
}
}
}
/** 检测打乱后是否可还原 */
if (reverseAmount % 2 !=0) {
/** 不合格,重新打乱 */
getRandomBlockList();
}
}
打乱拼图部分直接用数组的sort方法进行随机打乱:
blockList.sort(function () {
return 0.5 - Math.random();
});
其实打乱算法有很多种,我这里采用最粗暴的方法,也就是随机打乱。这种算法简单是简单,坏在可能出现无法复原的现象。针对这个问题,就有配套的检测打乱后是否可还原的算法,具体的算法理论我借用lufy大神的评论:
此类游戏能否还原关键是看它打乱后的逆序次数之和是否为偶数
假设你打乱后的数组中的每一个小图块为obj0,obj1,obj2,…它们打乱之前的序号分别为obj0.num,obj1.num…
接下来循环数组,如果前面元素的序号比此元素后某个元素的序号大,如obj0.num > obj1.num或者obj2.num > obj4.num就表示一个逆序
当全部的逆序之和为奇数时表示不可还原,重新打乱即可,打乱后重新检测,直到逆序之和为偶数为止
举个例子,如果有一个数组为[3, 4, 2, 1],那么里面3 2, 3 1, 2 4, 4 1, 2 1是逆序的,所以逆序数是5。
上面我给出的getRandomBlockList里的代码就是在实现打乱算法和检测是否可还原算法。
还有一种打乱方式,大家可以尝试尝试:和复原拼图一样,将空白块一步一步地与周围的拼图随机交换顺序。这个打乱算法较上一种而言,不会出现无法复原的现象,而且可以根据打乱的步数设定游戏难度。
在完成打乱拼图块后,如期而至的是显示拼图块:
function showBlock() {
for (var i=0, l=blockList.length; i < l; i++) {
var b=blockList[i];
/** 根据序号计算拼图块位置 */
var y=(i / 3) >>> 0, x=i % 3;
b.setLocation(x, y);
gameLayer.addChild(b);
}
}
显示了拼图块后,我们要做的就是添加操作拼图块的功能。于是需要拓展Block类,为其添加事件监听器onClick方法:
Block.prototype.onClick=function (e) {
var self=e.currentTarget;
if (isGameOver) {
return;
}
var checkList=new Array();
/** 判断右侧是否有方块 */
if (self.locationX > 0) {
checkList.push(Block.getBlock(self.locationX - 1, self.locationY));
}
/** 判断左侧是否有方块 */
if (self.locationX < 2) {
checkList.push(Block.getBlock(self.locationX + 1, self.locationY));
}
/** 判断上方是否有方块 */
if (self.locationY > 0) {
checkList.push(Block.getBlock(self.locationX, self.locationY - 1));
}
/** 判断下方是否有方块 */
if (self.locationY < 2) {
checkList.push(Block.getBlock(self.locationX, self.locationY + 1));
}
for (var i=0, l=checkList.length; i < l; i++) {
var checkO=checkList[i];
/** 判断是否是空白拼图块 */
if (checkO.index==8) {
steps++;
updateStepsTxt();
Block.exchangePosition(self, checkO);
break;
}
}
};
首先,我们在这里看到了isGameOver全局变量的作用,即在游戏结束后,阻断点击拼图块后的操作。
在点击了拼图块后,我们先获取该拼图块周围的拼图块,并将它们装入checkList,再遍历checkList,当判断到周围有空白拼图块后,即周围有index属性等于8的拼图块后,先更新操作步数,然后将这两个拼图块交换位置。具体交换拼图块位置的方法详见如下代码:
Block.exchangePosition=function (b1, b2) {
var b1x=b1.locationX, b1y=b1.locationY,
b2x=b2.locationX, b2y=b2.locationY,
b1Index=b1y * 3 + b1x,
b2Index=b2y * 3 + b2x;
/** 在地图块数组中交换两者位置 */
blockList.splice(b1Index, 1, b2);
blockList.splice(b2Index, 1, b1);
/** 交换两者显示位置 */
b1.setLocation(b2x, b2y);
b2.setLocation(b1x, b1y);
/** 判断游戏是否结束 */
Block.isGameOver();
};
还有就是Block.getBlock静态方法,用于获取给定的“数组位置”下的拼图块:
Block.getBlock=function (x, y) {
return blockList[y * 3 + x];
};
在Block.exchangePosition中,我们通过Block.isGameOver判断玩家是否已将拼图复原:
Block.isGameOver=function () {
var reductionAmount=0, l=blockList.length;
/** 计算还原度 */
for (var i=0; i < l; i++) {
var b=blockList[i];
if (b.index==i) {
reductionAmount++;
}
}
/** 计算是否完全还原 */
if (reductionAmount==l) {
/** 游戏结束 */
gameOver();
}
};
到这里,我们就实现了打乱和操作拼图块部分。
最后30min用于细枝末节上的处理,如显示拼图缩略图、显示&更新时间和步数,以及添加游戏结束画面,这些就交给如下冗长而简单的代码来完成吧:
function showThumbnail() {
var thumbnail=new LBitmap(imgBmpd);
thumbnail.scaleX=130 / imgBmpd.width;
thumbnail.scaleY=130 / imgBmpd.height;
thumbnail.x=(LGlobal.width - 100) /2;
thumbnail.y=410;
overLayer.addChild(thumbnail);
}
function addTimeTxt () {
timeTxt=new LTextField();
timeTxt.stroke=true;
timeTxt.lineWidth=3;
timeTxt.lineColor="#54D9EF";
timeTxt.color="#FFFFFF";
timeTxt.size=18;
timeTxt.x=20;
timeTxt.y=450;
overLayer.addChild(timeTxt);
updateTimeTxt();
}
function updateTimeTxt () {
timeTxt.text="时间:" + getTimeTxt(time);
}
function getTimeTxt () {
var d=new Date(time);
return d.getMinutes() + " : " + d.getSeconds();
};
function addStepsTxt () {
stepsTxt=new LTextField();
stepsTxt.stroke=true;
stepsTxt.lineWidth=3;
stepsTxt.lineColor="#54D9EF";
stepsTxt.color="#FFFFFF";
stepsTxt.size=18;
stepsTxt.y=450;
overLayer.addChild(stepsTxt);
updateStepsTxt();
}
function updateStepsTxt () {
stepsTxt.text="步数:" + steps;
stepsTxt.x=LGlobal.width - stepsTxt.getWidth() - 20;
}
function onFrame () {
if (isGameOver) {
return;
}
/** 获取当前时间 */
var currentTime=(new Date()).getTime();
/** 计算使用的时间并更新时间显示 */
time=currentTime - startTime;
updateTimeTxt();
}
function gameOver () {
isGameOver=true;
var resultLayer=new LSprite();
resultLayer.filters=[new LDropShadowFilter()];
resultLayer.graphics.drawRoundRect(3, "#BBBBBB", [0, 0, 350, 350, 5], true,"#DDDDDD");
resultLayer.x=(LGlobal.width - resultLayer.getWidth()) / 2;
resultLayer.y=LGlobal.height / 2;
resultLayer.alpha=0;
overLayer.addChild(resultLayer);
var title=new LTextField();
title.text="游戏通关"
title.weight="bold";
title.stroke=true;
title.lineWidth=3;
title.lineColor="#555555";
title.size=30;
title.color="#FFFFFF";
title.x=(resultLayer.getWidth() - title.getWidth()) / 2;
title.y=30;
resultLayer.addChild(title);
var usedTimeTxt=new LTextField();
usedTimeTxt.text="游戏用时:" + getTimeTxt(time);
usedTimeTxt.size=20;
usedTimeTxt.stroke=true;
usedTimeTxt.lineWidth=2;
usedTimeTxt.lineColor="#555555";
usedTimeTxt.color="#FFFFFF";
usedTimeTxt.x=(resultLayer.getWidth() - usedTimeTxt.getWidth()) / 2;
usedTimeTxt.y=130;
resultLayer.addChild(usedTimeTxt);
var usedStepsTxt=new LTextField();
usedStepsTxt.text="所用步数:" + steps;
usedStepsTxt.size=20;
usedStepsTxt.stroke=true;
usedStepsTxt.lineWidth=2;
usedStepsTxt.lineColor="#555555";
usedStepsTxt.color="#FFFFFF";
usedStepsTxt.x=usedTimeTxt.x;
usedStepsTxt.y=180;
resultLayer.addChild(usedStepsTxt);
var hintTxt=new LTextField();
hintTxt.text="- 点击屏幕重新开始 -";
hintTxt.size=23;
hintTxt.stroke=true;
hintTxt.lineWidth=2;
hintTxt.lineColor="#888888";
hintTxt.color="#FFFFFF";
hintTxt.x=(resultLayer.getWidth() - hintTxt.getWidth()) / 2;
hintTxt.y=260;
resultLayer.addChild(hintTxt);
LTweenLite.to(resultLayer, 0.5, {
alpha : 0.7,
y : (LGlobal.height - resultLayer.getHeight()) / 2,
onComplete : function () {
/** 点击界面重新开始游戏 */
stageLayer.addEventListener(LMouseEvent.MOUSE_UP, function () {
gameLayer.removeAllChild();
overLayer.removeAllChild();
stageLayer.removeAllEventListener();
startGame();
});
}
});
}
Ok,2h下来,整个游戏就搞定咯~不得不表扬一下lufylegend这个游戏引擎,实在是可以大幅提升开发效率。
最后奉上源代码:
由于头条禁止在文章页面加入链接,大家私信我“拼图”即可获取下载地址。
这篇博文在最初写成的时候,我没有对逆序算法进行深入研究,再加上我的测试不仔细,我没有发现算法的错误之处。因此,在博文发布后,不少读者发现游戏无解现象并将此问题反馈给了我,经过网友热心帮助,我才找到了问题所在,并更正了算法。在此对这些热心的网友表示真心的感谢,也为我学习不深入,以及误导了不少读者而感到十分内疚自责。
如果大家对本文有任何意见或不解,欢迎留言~
*请认真填写需求信息,我们会在24小时内与您取得联系。