【css3】我也来画个魔方

老姚
老姚 发布于 2016-11-04 10:01:01 浏览:6194 类型:原创 - 随笔 分类:JavaScript - 思路 二维码: 作者原创 版权保护
<style>
body{
	background:black;
	overflow:hidden;
	transform-style:preserve-3d;
}
.container{
	width:100px;
	height:100px;
	position:absolute;
	transform-style:preserve-3d;
	transform:rotateX(-45deg) rotateY(30deg) translate(-50%,-50%);
	top:40%;
	left:50%;
}
ul{
	list-style:none;
	padding-left:0px;
	width:100px;
	height:100px;
	position:absolute;
	transform-style:preserve-3d;
}
ul li{
	width:100px;
	height:100px;
	position:absolute;
	background:gray;
}
ul li:nth-of-type(1) {
	transform:translateX(-50px) rotateY(-90deg);
}
ul li:nth-of-type(2) {
	transform:translateY(-50px) rotateX(90deg);
}
ul li:nth-of-type(3) {
	transform:translateZ(50px) rotateY(0deg);
}
ul li:nth-of-type(4) {
	transform:translateZ(-50px) rotateY(180deg);
}
ul li:nth-of-type(5) {
	transform:translateY(50px) rotateX(90deg);
}
ul li:nth-of-type(6) {
	transform:translateX(50px) rotateY(90deg);
}
/*left face*/
ul[data-x="-1"] li:nth-of-type(1){
	background:purple;
}

/*right face*/
ul[data-x="1"] li:nth-of-type(6){
	background:red;
}

/*top face*/
ul[data-y="-1"] li:nth-of-type(2){
	background:blue;
}

/*bottom face*/
ul[data-y="1"] li:nth-of-type(5){
	background:green;
}

/*front face*/
ul[data-z="1"] li:nth-of-type(3){
	background:white;
}

/*back face*/
ul[data-z="-1"] li:nth-of-type(4){
	background:yellow;
}
ul li span{
	color:silver;
	font-size:30px;
	display:block;
	width:30px;
	height:30px;
	position:absolute;
	text-align:center;
	line-height:30px;
	display:none;
}
ul li span:nth-of-type(1){
	top:35px;
	left:10px;
}
ul li span:nth-of-type(2){
	top:10px;
	left:35px;
}
ul li span:nth-of-type(3){
	top:35px;
	right:10px;
}
ul li span:nth-of-type(4){
	bottom:10px;
	left:35px;
}
ul li:hover span{
	display:block;
}
ul li:hover span:hover{
	cursor:pointer;
}
</style>
<style id="keyframes"></style>
<div class="container"></div>
<script type="text/template" id="template">
	<li>
		<span data-action="left">⇐</span>
		<span data-action="up">⇑</span>
		<span data-action="right">⇒</span>
		<span data-action="down">⇓</span>
	</li>
</script>
<script>

var cube = {

	init: function() {
		// 生成dom节点
		var container = document.querySelector('.container');
		var template = document.querySelector('#template');
		var indexs = [-1, 0, 1];
		var num = 1;
		this.blocks = [];
		for (var k = 0; k < 3; k++) {
			for (var j = 0; j < 3; j++) {
				for (var i = 0; i < 3; i++) {
					var ul =  document.createElement('ul');
					var transform = ['translateX(', indexs[i] * 110, 'px)',
									'translateY(', indexs[j] * 110, 'px)',
									'translateZ(', indexs[k] * 110, 'px)'].join('');
					ul.style.setProperty('transform', transform);
					
					ul.setAttribute('data-num', num++);
					
					ul.setAttribute('data-x', indexs[i]);
					ul.setAttribute('data-y', indexs[j]);
					ul.setAttribute('data-z', indexs[k]);
					ul.setAttribute('data-coordinate', [indexs[i], indexs[j], indexs[k]].join(''));
					ul.x = indexs[i];
					ul.y = indexs[j];
					ul.z = indexs[k];
					
					var html = template.innerHTML;
					var htmls = [html, html, html, html, html, html];
					ul.innerHTML = htmls.join('');
					this.blocks.push(ul);
					container.appendChild(ul);
				}
			}
		}
		this.bindEvent();
	},
	
	bindEvent: function() {
		var self = this;
		document.addEventListener('click', function(e) {
			var target = e.target;
			if (target.nodeName.toLowerCase() == 'span') {
				var action;
				(action = target.getAttribute('data-action')) && this.actionDirector(target.parentNode, action);
				e.stopPropagation();
			}
		}.bind(this));
	},
	
	actionDirector: function(li, action) {
		var ul = li.parentNode;
		var index = [].indexOf.call(ul.children, li);
		var mapping = ['left', 'top', 'front', 'back', 'bottom', 'right'];
		var faceName = mapping[index];
		var configs = {
			left: {
				up: {
					axis: 'z',
					direction: 1
				},
				down: {
					axis: 'z',
					direction: -1
				},
				left: {
					axis: 'y',
					direction: -1
				},
				right: {
					axis: 'y',
					direction: 1
				}
			},
			right: {
				up: {
					axis: 'z',
					direction: -1
				},
				down: {
					axis: 'z',
					direction: 1
				},
				left: {
					axis: 'y',
					direction: -1
				},
				right: {
					axis: 'y',
					direction: 1
				}
			},
			top: {
				up: {
					axis: 'x',
					direction: 1
				},
				down: {
					axis: 'x',
					direction: -1
				},
				left: {
					axis: 'z',
					direction: -1
				},
				right: {
					axis: 'z',
					direction: 1
				}
			},
			bottom: {
				up: {
					axis: 'x',
					direction: -1
				},
				down: {
					axis: 'x',
					direction: 1
				},
				left: {
					axis: 'z',
					direction: 1
				},
				right: {
					axis: 'z',
					direction: -1
				}
			},
			front: {
				up: {
					axis: 'x',
					direction: 1
				},
				down: {
					axis: 'x',
					direction: -1
				},
				left: {
					axis: 'y',
					direction: -1
				},
				right: {
					axis: 'y',
					direction: 1
				}
			},
			back: {
				up: {
					axis: 'x',
					direction: -1
				},
				down: {
					axis: 'x',
					direction: 1
				},
				left: {
					axis: 'y',
					direction: -1
				},
				right: {
					axis: 'y',
					direction: 1
				}
			}
		};
		var config = configs[faceName][action];
		this.changeKeyframes(config.axis, ul[config.axis], config.direction);
		this.rotateFace(config.axis, ul[config.axis], config.direction).forEach(function(block) {
			block.className = 'rotate' + block.getAttribute('data-coordinate');
		})
	},
	
	changeKeyframes: function(axis, index, direction) {
		var face = this.getFace(axis, index);
		var style = document.querySelector('#keyframes');
		style.innerHTML = "";
		for (var i = 0; i < face.length; i++) {
			var transform = getComputedStyle(face[i]).getPropertyValue('transform');
			var x = face[i].x;
			var y = face[i].y;
			var z = face[i].z;
			
			var className = "rotate" + face[i].getAttribute('data-coordinate');
			var origin = {
				x: [(-x) * 110 +  50, (-y) * 110 + 50, (-z) * 110],
			}
			var origin = origin.x[0] + 'px ' + origin.x[1] + 'px ' + origin.x[2] + 'px ';
			style.innerHTML += "." + className + " { animation: " + className + " 1s linear backwards; transform-origin: " + origin + "} @keyframes " + className + "{ to{ transform:" + transform + " rotate" + axis.toUpperCase() + "(" + direction * 90 + "deg); } }"
		}
	},
	
	// 获取一面 比如x轴第1面 axis = 'x' index = 1
	getFace: function(axis, index) {
		return this.blocks.filter(function(block) {
			return block[axis] == index;
		});
	},
	
	// 获取一条 
	getBar: function(axis, index, faceName) {
		var face = this.getFace(axis, index);
		var configs = {
			x: {
				front: {
					axis: 'z',
					index: 1,
					sortAxis: 'y',
					sortDirection: 1,
					faceIndex: 3
				},
				back: {
					axis: 'z',
					index: -1,
					sortAxis: 'y',
					sortDirection: -1,
					faceIndex: 4
				},
				top: {
					axis: 'y',
					index: -1,
					sortAxis: 'z',
					sortDirection: 1,
					faceIndex: 2
				},
				bottom: {
					axis: 'y',
					index: 1,
					sortAxis: 'z',
					sortDirection: -1,
					faceIndex: 5
				}
			},
			y: {
				front: {
					axis: 'z',
					sortAxis: 'x',
					sortDirection: 1,
					index: 1,
					faceIndex: 3
				},
				back: {
					axis: 'z',
					sortAxis: 'x',
					sortDirection: -1,
					index: -1,
					faceIndex: 4
				},
				left: {
					axis: 'x',
					sortAxis: 'z',
					sortDirection: 1,
					index: -1,
					faceIndex: 1
				},
				right: {
					axis: 'x',
					sortAxis: 'z',
					sortDirection: -1,
					index: 1,
					faceIndex: 6
				}
			},
			z: {
				top: {
					axis: 'y',
					sortAxis: 'x',
					sortDirection: 1,
					index: -1,
					faceIndex: 2
				},
				bottom: {
					axis: 'y',
					sortAxis: 'x',
					sortDirection: -1,
					index: 1,
					faceIndex: 5
				},
				left: {
					axis: 'x',
					sortAxis: 'y',
					sortDirection: -1,
					index: -1,
					faceIndex: 1
				},
				right: {
					axis: 'x',
					sortAxis: 'y',
					sortDirection: 1,
					index: 1,
					faceIndex: 6
				}
			}
		};
		var config = configs[axis][faceName];
		return face.filter(function(block) {
			return block[config.axis] == config.index;
		}).sort(function(a, b) {
			if (a[config.sortAxis] < b[config.sortAxis]) {
				return -1 * config.sortDirection;
			}
			if (a[config.sortAxis] > b[config.sortAxis]) {
				return 1 * config.sortDirection;
			}
			return 0;
		}).map(function(block) {
			var a, b;
			(axis == 'x') && (a = 1, b = 6);
			(axis == 'y') && (a = 2, b = 5);
			(axis == 'z') && (a = 3, b = 4);
			return [block.querySelector('li:nth-of-type(' + config.faceIndex + ')'), 		
				block.querySelector('li:nth-of-type(' + a + ')'),
				block.querySelector('li:nth-of-type(' + b + ')')
			];
		});
	},
	
	// axis表示坐标轴 'x'、'y'、'z'
	// index-1 0 1 左中右
	// direction 1 -1 旋转方向
	rotateFace: function(axis, index, direction) {
		var results = this.getFace(axis, index);
		var self = this;
		var length = results.length;
		var block = results[length - 1];
		block.end && block.removeEventListener('animationend', block.end, false);
		
		block.addEventListener('animationend', block.end = function rotate() {
			if (rotate.already) return;
			rotate.already = true;
			self.changeColor(axis, index, direction);
		}, false);
		return results;
	},
	

	changeColor: function(axis, index, direction) {
		var face = this.getFace(axis, index);
		var configs = {
			x :['front', 'top', 'back', 'bottom', 'front'],
			y: ['front', 'right', 'back', 'left', 'front'],
			z: ['top', 'right', 'bottom', 'left', 'top']
		};
		var config = (direction == 1) ? configs[axis] : configs[axis].reverse();

		var self = this;
		var arr = config.map(function(faceName) {
			return self.getBar(axis, index, faceName);
		});
		
		var colors = arr.map(function(array) {
			return array.map(function(lists) {
				return lists.map(function(li) {
					return getComputedStyle(li).getPropertyValue('background-color');
				})
			});
		});
		arr.forEach(function(array, i) {
			array.forEach(function(list, j) {
				list.forEach(function(li, k) {
					if ((i > 0) ) {
						li.style.setProperty('background-color', colors[i - 1][j][k])
					}
				});
			});
		});
	}
};
cube.init();
</script>
<script>
var flag;
function direction(e) {
	var position = direction.position = direction.position || {x: 0, y: 0};
	var x = e.x;
	var y = e.y;
	var r = {};
	r.x = Math.abs(y - position.y) < 2 ? 0 : y < position.y ? 1 : -1;
	r.y = Math.abs(x - position.x) < 2 ? 0 : x < position.x ? -1 : 1;
	direction.position.x = x;
	direction.position.y = y;
	return r;
}
var container = document.querySelector('.container');
document.addEventListener('mousemove', function(e) {
	if (!flag) return; 
	var transform = getComputedStyle(container).getPropertyValue('transform');
	var r = direction(e);
	container.style.setProperty('transform', transform + 'rotateX(' + r.x * 2 + 'deg) rotateY(' + r.y * 1.5  + 'deg)');
}, false);

document.addEventListener('mousedown', function(e) {
	flag = true;
}, false);
document.addEventListener('mouseup', function(e) {
	flag = false;
}, false);
</script>



这点代码用了三天,才写出来。

自从知道css3的3d变换技术以来,就一直想如何实现个魔方。
后来在本站上等了一年,确实有人画了出来。点击这里
当时看了之后确实感觉到振奋,心想心中一个愿望,终于实现了。虽然是别人先做成的。。。

前两天又看了一下,了解其原理后,觉得很简单,于是决定自己动手搞个。
现在搞完了,只想说一句,谁做谁知道。
本文并没有怎么参考其代码(看了10分钟,没看进去,人家算法是支持任意阶的),按照自己的思路实现的。
三个通宵,到现在还没怎么睡好过。一闭眼就想这个,脑海里始终有个魔方在那旋转着。

用到的技术
1.基本3d变换
终于熟悉了3d旋转方向规则,左手定则!

2.动画
keyframe是样式规则。
如果里面有变量怎么办呢?
js动态生成样式的问题。

3.魔方知识
实现原理,正如那篇文章里说的那样。其实元素并没有动,只是换了颜色。

4.认识到了前端动画技术有待发展啊
谁做谁知道啊,太局限了。
比如,animation熟悉设置forwards可以保持动画最终效果,但是并没有提供新的位置信息。

5.代码风格稍微简单一些
用了策略模式和模板,没怎么封装,只是为了实现效果去的。

6.有待完善的地方
没有搞事件队列、
代码有待优化、肯定有优化的算法,
当时只想实现3阶魔方来的。

貌似终于可以好好睡觉了,
最近72小时里,只睡了12小时,实在睡不着。我已经入魔,大脑不转时就拿手比划。
期间除了吃饭和重温一下《阿凡达》之外,其余都在研究这个东西,5%的时间写代码,各种调试。艾玛~~
其实有时一些事情,你做完之后,会觉得这么简单的方式,怎么没有一开始咋就没想到呢!
个人觉得魔方这个东西可以作为你的一个小练习。

我现在在想,能把这个东西做成教学视频那就好了。
最后来句,
纸上得来终觉浅,绝知此事要躬行。

本文完。
z
给个赞 99 人点赞
收藏 50 人收藏
评论 已有 45 条评论;以下用户言论只代表其个人观点,不代表 前端网(QDFuns) 的观点或立场。
登录 以后才能发表评论
最热评论
dfmily27
dfmily272016-11-04 15:26:1916F
哼,还好我不会玩魔方,要不然这一下午都搁这儿玩魔方了。。
举报 支持 (3) 回复 (1)
最新评论
18792212799
187922127992016-12-27 11:20:4445F
好厉害
举报 支持 (0) 回复 (0)
阿沁的前端之路
阿沁的前端之路2016-12-21 21:45:4444F
纸上得来终觉浅,觉知此事要躬行。emoticon
举报 支持 (0) 回复 (0)
老姚
老姚2016-11-11 18:35:0943F
应该可以,需要使用webgl吧。因为是3d的。要我实现的话,还得个人学6了才行。 //@405000360:为什么不用canvas 写呢
举报 支持 (0) 回复 (0)
405000360
4050003602016-11-11 17:42:3142F
为什么不用canvas 写呢
举报 支持 (0) 回复 (1)
chenyi_web
chenyi_web2016-11-09 10:07:0041F
牛逼
举报 支持 (0) 回复 (0)
皮皮鲁的长袜子
皮皮鲁的长袜子2016-11-08 17:28:0940F
准备找年薪多少的啊? //@老姚:没,在家呆着,得去找工作了。 //@皮皮鲁的长袜子:老姚,你要去当老师了么?算我一个学生可好?不要收我费哈
举报 支持 (0) 回复 (0)
kait1314
kait13142016-11-08 12:57:1539F
厉害了,我的哥emoticon
举报 支持 (0) 回复 (0)
老姚
老姚2016-11-07 23:59:4838F
好像很麻烦。。。 //@小单纯:老姚  怎么不出视频教程呢?
举报 支持 (0) 回复 (0)
老姚
老姚2016-11-07 23:59:0137F
没,在家呆着,得去找工作了。 //@皮皮鲁的长袜子:老姚,你要去当老师了么?算我一个学生可好?不要收我费哈
举报 支持 (0) 回复 (1)
858418616
8584186162016-11-07 21:23:2236F
厉害
举报 支持 (0) 回复 (0)
皮皮鲁的长袜子
皮皮鲁的长袜子2016-11-07 17:32:2335F
老姚,你要去当老师了么?算我一个学生可好?不要收我费哈
举报 支持 (0) 回复 (1)
Sakura
Sakura2016-11-07 09:12:2534F
太厉害。。。
举报 支持 (0) 回复 (0)
小单纯
小单纯2016-11-06 20:19:1833F
老姚  怎么不出视频教程呢?
举报 支持 (0) 回复 (1)
doterlin
doterlin2016-11-05 18:14:0932F
你为何那么吊
举报 支持 (0) 回复 (0)
2586645609
25866456092016-11-05 16:51:2531F
厉害
举报 支持 (0) 回复 (0)
老姚 老姚 作者

与自己为敌,与自己为友,一边深挖思想,一边埋葬自己。

作者最新