Catalog :
— 总体感想
— 如何用canvas画图
— 关于页面缓动跳转的探索
——- CSS3动画
——- 缓动函数
— 用HTML5自定义属性Dataset实现自定义信息储存
— 从老司机那里学到的产品开发版本问题
— 后期学习:
——- 自适应调节问题
总体感想
因为决定以后做前端方向的工作,所以把参加这次比赛的初衷确定为学习网页搭建的基本流程,和小伙伴一起决定不使用UI框架,手撸了一个网页出来,网页内容是天文社团的主页,但是问题同样严重,遇到不少坑,也想了不少办法解决,最后还是差强人意,但是对我来说经验的积累和总结更重要,接下来同样要学习更多的姿势,不管怎样感谢队友的包容和努力,我在这里对这次比赛学到的东西进行总结,再加上结束后学习到的一些东西,包括自适应和CSS的布局;然后接下来可能会把学习重点放到JS上。
如何用canvas画图
使用canvas的初衷是我们打算用好看的动画作为背景,后来因为掉帧严重,优化不来放弃这个方案,但是同样在其中学习到用canvas画图的乐趣。
最开始我们实现的是这样背景效果;
实现JS代码如下:
var context;
var starsnum = 150; //星星数量
var mouse_pre = {
x: 0,
y: 0
};
var mouse_cur = {
x: 0,
y: 0
};
var flag = true;
var starArr = []; //储存每一个星星的位置
var movArrX = []; //储存移动变化的因子
var movArrY = [];
function random(lo, hi) { //随机获得从lo-hi的整数
return Math.floor(Math.random() * (hi - lo + 1) + lo);
}
function init() { //初始化
canvas = document.getElementById("canvas");
context = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
canvas.style.cssText = "top: 0";
for (var i = 0; i < starsnum; i++) {
x = random(0, canvas.width );
y = random(0, canvas.height);
r = 1;
var newStar = drawCirle(context, x, y, r);
starArr.push(newStar);
var mov_arrX = random(-1,1);
var mov_arrY = random(-1,1);
while(mov_arrX == 0 || mov_arrY == 0){
mov_arrX = random(-1,1);
mov_arrY = random(-1,1);
}
movArrX.push(mov_arrX/5);
movArrY.push(mov_arrY/5);
}
for (var j = 0; j < starsnum; j++) {
for (var k = j; k < starsnum; k++) {
var bx = starArr[j].centerX;
var by = starArr[j].centerY;
var cx = starArr[k].centerX;
var cy = starArr[k].centerY;
var dis = Math.sqrt(Math.abs(cx - bx) * Math.abs(cx - bx) + Math.abs(by - cy) * Math.abs(by - cy));
if (dis < 0.045 * (canvas.width + canvas.height) / 2) {
drawLine(context, bx, by, cx, cy);
}
}
}
// window.addEventListener('mousemove', mouseMove, false); //鼠标移动监控
render();
}
// function mouseMove(event) { //鼠标移动事件
// // if (flag) {
// // mouse_pre.x = event.screenX;
// // mouse_pre.y = event.screenY;
// // flag = false;
// // }
// mouse_cur.x = event.screenX;
// mouse_cur.y = event.screenY;
// render();
// mouse_pre.x = mouse_cur.x;
// mouse_pre.y = mouse_cur.y;
// }
function render() {
context.clearRect(0, 0, canvas.width, canvas.height);
// dx = mouse_cur.x - mouse_pre.x;
// dy = mouse_cur.y - mouse_pre.y;
for (var i = 0; i < starsnum; i++) {
var changeStar = starArr[i];
changeStar.centerX += movArrX[i];
changeStar.centerY += movArrY[i];
if(changeStar.centerX>canvas.width || changeStar.centerX < 0){
movArrX[i] = -movArrX[i];
}
if(changeStar.centerY>canvas.height || changeStar.centerY < 0){
movArrY[i] = -movArrY[i];
}
drawCirle(context, changeStar.centerX, changeStar.centerY, changeStar.radius);
starArr[i] = changeStar;
}
for (var j = 0; j < starsnum; j++) {
for (var k = j; k < starsnum; k++) {
var bx = starArr[j].centerX;
var by = starArr[j].centerY;
var cx = starArr[k].centerX;
var cy = starArr[k].centerY;
var dis = Math.sqrt(Math.abs(cx - bx) * Math.abs(cx - bx) + Math.abs(by - cy) * Math.abs(by - cy));
if (dis < 0.045 * (canvas.width + canvas.height) / 2) {
drawLine(context, bx, by, cx, cy);
}
}
}
window.requestAnimationFrame(render);
}
function drawCirle(ctx, x, y, r) { //在(x,y)处画半径为r的圆
function Circle(x, y, r) {
this.centerX = x;
this.centerY = y;
this.radius = r;
}
var circle = new Circle(x, y, r);
ctx.beginPath();
ctx.arc(circle.centerX, circle.centerY, circle.radius, 0, Math.PI * 2, true);
ctx.fillStyle = "rgba(255,255,255,1)";
ctx.fill();
ctx.closePath();
return circle;
}
function drawLine(ctx, bx, by, cx, cy) { //用直线把(bx,by)到(cx,cy)连起来
function Line(bx, by, cx, cy) {
this.beginX = bx;
this.beginY = by;
this.closeX = cx;
this.closeY = cy;
}
var line = new Line(bx, by, cx, cy);
ctx.beginPath();
ctx.moveTo(line.beginX, line.beginY);
ctx.lineTo(line.closeX, line.closeY);
ctx.stroke();
ctx.lineWidth = 1;
ctx.strokeStyle = "rgba(255,255,255,0.3)"
ctx.closePath();
}
init();
这里我简单对代码进行分析,首先是starsnum表示星星的总数,再用starArr[] 对象数组来储存每个星星的位置,因为星星是自己移动的所以给星星一个初始的移动距离,用movArrX[]和movArrY[] 分别表示在X,Y方向上的移动因子,注意在浏览器窗口中坐标轴的位置,以左上角为坐标原点,向下为Y轴正半轴,向右为X轴正半轴;接下来是初始化函数,在初始化函数中用context = canvas.getContext(“2d”);来新建一个画布,window.innerWidth 和 window.innerHeight分别表示浏览器的宽高;然后随机星星的位置,半径为1,调用drawCirle()函数来画出一个星星;
在画星星的函数中beginPath()和closePath()表示画图的操作写在里面,arc()表示画出弧,然后在参数中Math.PI * 2表示弧的长度为圆的周长,也就是画出一个圆,然后用fillStyle和fill()填充圆,这样一个星星就画好了;
星星画好后把这个星星放入starArr数组中,再随机mov_arrX 和 mov_arrY 每个星星初始时在X,Y方向上的每次的移动因子,可以通过这个来控制移动的速度快慢;
之后是连线,连线就是在两个点的距离小于某个值时把两个点连线,关于判断两个点的距离我就不说了,我说说调用的drawLine()画线函数,同画星星那样,用beginPath()和closePath()表示画图操作区域,moveTo()和lineTo()表示线起点位置和终点位置,stroke()表示开始画线,strokeStyle 表示样式,这样线也连起来了;
下面是最重要的render()函数,在函数中我们重新画整幅图,把每个星星的位置加上他们的移动因子,就是他们接下来的位置,然后重新判断是否连线,然后用window.requestAnimationFrame(render)回调render(),这样实现连续的作画,关于为何使用requestAnimationFrame()而不是使用setTimeout()可以参见这篇博客 主要是因为浏览器的显示频率即绘制频率和requestAnimationFrame()同步,而setTimeout()需要自己调整,很容易出问题,最开始就是这么用的导致掉帧严重,就舍弃了这个方案,也还有队友博客中提到的每次都重画整个画布会有较大的资源占用。
除了这个背景我们还看了这里的demo5和demo7这种背景,但是队友觉得不好看也就没用上,其中画图的思路大同小异,就看自己的创造力了,还是相当有难度的,特别是我看到demo4是一个3D的效果,以后或许会认真看看,学习学习;
关于页面缓动跳转的探索
因为没打算用UI框架,所以关于缓动这一块可是想了好久,首先是元素缓动使用CSS3动画,也就是在CSS里面绑定变化,然后用@keyframes 规则创建动画。在 @keyframes 中规定某项 CSS 样式,就能创建由当前样式逐渐改为新样式的动画效果。
CSS中绑定,写上绑定哪个和动画时间:
div
{
animation: myfirst 5s;
-moz-animation: myfirst 5s; /* Firefox */
-webkit-animation: myfirst 5s; /* Safari 和 Chrome */
-o-animation: myfirst 5s; /* Opera */
}
写上变化的样式和怎么变化:
@keyframes myfirst
{
from {background: red;}
to {background: yellow;}
}
@-moz-keyframes myfirst /* Firefox */
{
from {background: red;}
to {background: yellow;}
}
@-webkit-keyframes myfirst /* Safari 和 Chrome */
{
from {background: red;}
to {background: yellow;}
}
@-o-keyframes myfirst /* Opera */
{
from {background: red;}
to {background: yellow;}
}
其中还可以调整动画的方式,比如:
linear 动画从头到尾的速度是相同的。
ease 默认。动画以低速开始,然后加快,在结束前变慢。
ease-in 动画以低速开始。
ease-out 动画以低速结束。
ease-in-out 动画以低速开始和结束。
或是自定义贝塞尔曲线:
cubic-bezier(n,n,n,n) 在 cubic-bezier 函数中自己的值。可能的值是从 0 到 1 的数值。
除了元素缓动,我们还思考如何实现,滑轮滚动一下自动滑动到下一页,在这里使用CSS3动画就不太好办了,所以我们找到了缓动函数Tween();其实缓动的思路可以参照之前说的画布画图,也就是通过回调函数重新绘制整个页面,再通过特定的更新函数来调整缓动的方式,也就是CSS3动画里的几种动画方式都有对应的函数实现。
这是Tween()的代码:
/* tween js
* t: current time(当前时间)
* b: beginning value(初始值)
* c: change in value(变化量)
* d: duration(持续时间)
*/
var Tween = {
Linear: function(t, b, c, d) {
return c * t / d + b;
},
Quad: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t + b;
},
easeOut: function(t, b, c, d) {
return -c * (t /= d) * (t - 2) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t + b;
return -c / 2 * ((--t) * (t - 2) - 1) + b;
}
},
Cubic: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t + b;
},
easeOut: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t + 1) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t + b;
return c / 2 * ((t -= 2) * t * t + 2) + b;
}
},
Quart: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t * t + b;
},
easeOut: function(t, b, c, d) {
return -c * ((t = t / d - 1) * t * t * t - 1) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t + b;
return -c / 2 * ((t -= 2) * t * t * t - 2) + b;
}
},
Quint: {
easeIn: function(t, b, c, d) {
return c * (t /= d) * t * t * t * t + b;
},
easeOut: function(t, b, c, d) {
return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return c / 2 * t * t * t * t * t + b;
return c / 2 * ((t -= 2) * t * t * t * t + 2) + b;
}
},
Sine: {
easeIn: function(t, b, c, d) {
return -c * Math.cos(t / d * (Math.PI / 2)) + c + b;
},
easeOut: function(t, b, c, d) {
return c * Math.sin(t / d * (Math.PI / 2)) + b;
},
easeInOut: function(t, b, c, d) {
return -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;
}
},
Expo: {
easeIn: function(t, b, c, d) {
return (t == 0) ? b : c * Math.pow(2, 10 * (t / d - 1)) + b;
},
easeOut: function(t, b, c, d) {
return (t == d) ? b + c : c * (-Math.pow(2, -10 * t / d) + 1) + b;
},
easeInOut: function(t, b, c, d) {
if (t == 0) return b;
if (t == d) return b + c;
if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
}
},
Circ: {
easeIn: function(t, b, c, d) {
return -c * (Math.sqrt(1 - (t /= d) * t) - 1) + b;
},
easeOut: function(t, b, c, d) {
return c * Math.sqrt(1 - (t = t / d - 1) * t) + b;
},
easeInOut: function(t, b, c, d) {
if ((t /= d / 2) < 1) return -c / 2 * (Math.sqrt(1 - t * t) - 1) + b;
return c / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + b;
}
},
Elastic: {
easeIn: function(t, b, c, d, a, p) {
var s;
if (t == 0) return b;
if ((t /= d) == 1) return b + c;
if (typeof p == "undefined") p = d * .3;
if (!a || a < Math.abs(c)) {
s = p / 4;
a = c;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
},
easeOut: function(t, b, c, d, a, p) {
var s;
if (t == 0) return b;
if ((t /= d) == 1) return b + c;
if (typeof p == "undefined") p = d * .3;
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
return (a * Math.pow(2, -10 * t) * Math.sin((t * d - s) * (2 * Math.PI) / p) + c + b);
},
easeInOut: function(t, b, c, d, a, p) {
var s;
if (t == 0) return b;
if ((t /= d / 2) == 2) return b + c;
if (typeof p == "undefined") p = d * (.3 * 1.5);
if (!a || a < Math.abs(c)) {
a = c;
s = p / 4;
} else {
s = p / (2 * Math.PI) * Math.asin(c / a);
}
if (t < 1) return -.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p)) + b;
return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * d - s) * (2 * Math.PI) / p) * .5 + c + b;
}
},
Back: {
easeIn: function(t, b, c, d, s) {
if (typeof s == "undefined") s = 1.70158;
return c * (t /= d) * t * ((s + 1) * t - s) + b;
},
easeOut: function(t, b, c, d, s) {
if (typeof s == "undefined") s = 1.70158;
return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
},
easeInOut: function(t, b, c, d, s) {
if (typeof s == "undefined") s = 1.70158;
if ((t /= d / 2) < 1) return c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b;
return c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
}
},
Bounce: {
easeIn: function(t, b, c, d) {
return c - Tween.Bounce.easeOut(d - t, 0, c, d) + b;
},
easeOut: function(t, b, c, d) {
if ((t /= d) < (1 / 2.75)) {
return c * (7.5625 * t * t) + b;
} else if (t < (2 / 2.75)) {
return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
} else if (t < (2.5 / 2.75)) {
return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
} else {
return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
}
},
easeInOut: function(t, b, c, d) {
if (t < d / 2) {
return Tween.Bounce.easeIn(t * 2, 0, c, d) * .5 + b;
} else {
return Tween.Bounce.easeOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
}
}
}
}
Math.tween = Tween;
可以直接在需要调用的时候传入参数进行使用。
function to_right() { //向右缓动
var wid = wrapper.offsetWidth;
console.log(wrapper.offsetWidth);
var after = document.body.clientWidth * 0.08;
console.log(document.body.clientWidth);
var start = 0;
var during = 10;
tween_to_right = function() {
start++;
var change = Tween.Quart.easeOut(start, wid, after - wid, during);
wrapper.style.width = change + "px";
if (start < during)
requestAnimationFrame(tween_to_right);
else {
pic_to_left.style.display = "block";
pic_to_right.style.display = "none";
wrapper.style.backgroundImage = "none";
}
};
tween_to_right();
box.style.display = "none";
}
这里是我使用到一段元素向右缓动的函数,可以看到同样是使用到了requestAnimationFrame() ,这里对需要改变的元素进行累计,这里就是宽度,然后每次更新后进行回调,从而实现缓动的效果。调用语句是Tween.Quart.easeOut(start, wid, after - wid, during);
;
用HTML5自定义属性Dataset实现自定义信息储存
这里使用HTML5自定义属性Dataset实现自定义信息储存
就是记录上方的按钮点击的信息,记录对应按钮的开关。我们看着代码进行讲解;
<input type="button" class = "active" id = "btn1" data-index = "1" />
<input type="button" class = "before_active" id = "btn2" data-index = "0"/>
<input type="button" class = "before_active" id = "btn3" data-index = "0" />
<input type="button" class = "before_active" id = "btn4" data-index = "0" />
可以看到这里有四个按钮,每个按钮有对应的id,和初始时他们的类分别为 active 表示选中这个按钮的CSS,before_active 表示不选中这个按钮CSS,还有就是我这边想到的用HTML5自定义属性Dataset。先简单介绍下Dataset 当然也可以参考大牛的博客,data-前缀用来储存你想储存数据,用dataset.前缀 在JS中来取到数据。
示例:
<div id="day2-meal-expense"
data-drink="coffee"
data-food="sushi"
data-meal="lunch">¥20.12</div>
var expenseday2 = document.getElementById('day2-meal-expense');
var typeOfDrink = expenseday2.dataset.drink;
这里的typeOfDrink就是”coffee”
接下来我说说我是怎么用它实现储存信息的。
这里是JS代码:
var box = document.getElementById('box');
var input = box.getElementsByTagName('input');
var div = box.getElementsByTagName('div');
var btn1 = document.getElementById("btn1");
var btn2 = document.getElementById("btn2");
var btn3 = document.getElementById("btn3");
var btn4 = document.getElementById("btn4");
for (var i = 0; i < input.length; i++) {
input[i].index = i;
input[i].onclick = function() {
for (var i = 0; i < input.length; i++) {
input[i].className = 'before_active';
div[i].style.display = 'none';
};
this.className = 'active';
div[this.index].style.display = 'block';
if (this.index == 0) {
wrapper.style.backgroundColor = "rgba(180,151,90, 0.3)";
btn1.dataset.index = 1;
btn2.dataset.index = 0;
btn3.dataset.index = 0;
btn4.dataset.index = 0;
} else if (this.index == 1) {
wrapper.style.backgroundColor = "rgba(0,128,128,0.3)";
btn1.dataset.index = 0;
btn2.dataset.index = 1;
btn3.dataset.index = 0;
btn4.dataset.index = 0;
} else if (this.index == 2) {
btn1.dataset.index = 0;
btn2.dataset.index = 0;
btn3.dataset.index = 1;
btn4.dataset.index = 0;
wrapper.style.backgroundColor = "rgba(13,37,76,0.3)";
} else if (this.index == 3) {
btn1.dataset.index = 0;
btn2.dataset.index = 0;
btn3.dataset.index = 0;
btn4.dataset.index = 1;
wrapper.style.backgroundColor = "rgba(197,195,214,0.3)";
}
};
};
可以看到当我们注册点击事件时,对当前的index进行了判断,然后用dataset.index修改信息的值,初始时是 1 0 0 0 当点击到谁就把其值修改为 1 其他修改为 0 表示开关,这样就对当前状态进行了记录,我在接下来就用这些信息进行缓动操作,以判断当前默认页面的值。所以也算极大的方便操作,特别妙的一种方法,值得学习!
从老司机那里学到的产品开发版本问题
因为根本没有工程相关的经验,这次也是两眼一抹黑,也是我比较大的问题,最后结束时我的文件夹里有各种版本的代码,自己都有点懵逼。。。如何高效的和别人合作,学习相应的项目维护和工程开发流程至关重要,在快结束时有老司机给我们讲了一下如何维护产品开发版本,我也在这里总结下,首先是一个master 分支,表示当前最完善的也是上线的版本,其他人从这个master分支fork出去进行对应的新功能的添加,如果有很多人同时对着这个功能进行添加或修改,那么在这个功能维护一个主分支,其他在这个主分支上进行修改和添加,当这个功能的所有冲突解决和完善后,在把这个分支和到主master上,这样就实现了一个新功能的上线和更新;在这次项目中,我是完全被这个问题搞懵了,主要是我们有两个人同时在做某个功能,最后用了完全不同的方法实现,各自各自的问题,或是意见起了冲突,而且因为不了解对方的方法,所以也没法解决冲突,我就放弃了这个功能,或是队友放弃,全部让一个人去做了,这样导致了时间的浪费和人力的浪费,也让人觉得不甘心啥的,功劳白费啥的。所以这也是今后做功能和分工需要注意的地方。
后期学习
下来后学着重学习了一些CSS上的东西,有CSS的自适应调节和布局,相关的博客已经很多了,这里我也简单提一提。
自适应调节
自适应在网页设计之初就需要思考好应该怎么做,不要等到网页写好后再来改,有时这样是很痛苦的,总结学习到的几点;
一. 允许网页宽度自动调整:
在网页代码的头部,加入一行viewport元标签。
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=0, width=device-width"/>
viewport是网页默认的宽度和高度,上面这行代码的意思是,网页宽度默认等于屏幕宽度(width=device-width),原始缩放比例(initial-scale=1)为1.0,即网页初始大小占屏幕面积的100%。
二. 不使用绝对宽度
由于网页会根据屏幕宽度调整布局,所以不能使用绝对宽度的布局,也不能使用具有绝对宽度的元素。这一条非常重要。具体说,CSS代码不能指定像素宽度: width:xxx px;
只能指定百分比来定义列宽度: width: xx%;
或者: width:auto;
或者:使用最大宽度和最大高度max-width,max-height;
三. 相对大小的字体
字体也不能使用绝对大小(px),而只能使用相对大小(em)。
或是使用vw相对于视口的宽度。视口被均分为100单位的vw,这样字体是可以跟随窗口大小变化的。
四. 流动布局(fluid grid)
"流动布局"的含义是,各个区块的位置都是浮动的,不是固定不变的。float的好处是,如果宽度太小,放不下两个元素,后面的元素会自动滚动到前面元素的下方,不会在水平方向overflow(溢出),避免了水平滚动条的出现。 另外,绝对定位(position: absolute)的使用,也要非常小心。
五. "自适应网页设计"的核心,就是CSS3引入的Media Query模块
@media screen and (max-device-width: 400px) {
.column {
float: none;
width:auto;
}
#sidebar {
display:none;
}
}
上面的代码意思是,如果屏幕宽度小于400像素,则column块取消浮动(float:none)、宽度自动调节(width:auto),sidebar块不显示(display:none)。
这次比赛收获良多,也暴露出不少问题,总之还是需要一步一个脚印的走吧!
以上仅代表个人观点,有错还望指出!