功能说明:

通过鼠标移动,实时绘制出3d旋转的线条。

兼容IE 5 6 7 8 9 10 firefox chrome

效果预览:

请按着鼠标左键,在画板上拖动绘制

实现原理:

  在上一篇文章《javascript椭圆旋转相册》中,通过图片绕椭圆轨迹移动实现视觉上的3d旋转,之前在某个网站中看到过这种效果,于是我想到其实我们也可以用同样原理,通过把点绕不同椭圆轨迹的运动实现任意线的3d旋转。当按下鼠标左键在画板上绘制时,每个点根据初始位置,确定其椭圆轨迹大小,以相同的速率改变每个点的位置,从而实现整条曲线的3d旋转。

代码分析:

init:function(id,options){//初始化函数                 options=options||{};this.container=document.getElementById(id||'container');this.centerLeft=this.container.clientWidth/2;//原点的left值this.centerTop=this.container.clientHeight/2;    //原点的top值this.maxA=options.maxA||300;//旋转椭圆轨迹的横半轴长                this.maxB=options.maxB||1;//旋转椭圆轨迹的竖半轴长                this.ballMargin=options.ballMargin||20;//绘画时小球与小球间间的距离                this.arr=[];//保存画板上所有小球的数组                this._id;//计时器Id

this.containerPos=getContainerPos(this.container);//容器位置

                bindHandler.call(this);this.run();            }

 

 先看初始化需要哪些值,要使所有点实现椭圆旋转,首先确定椭圆圆心的left和top值,这里椭圆圆心取容器的中点位置。之后确定的是椭圆轨迹的横半轴和竖半轴长,还有绘制时小球与小球之间的距离(ballMargin),距离越大,每个小球之间间隙越宽。另外我们需要获取容器的位置,为获取鼠标在容器内的位置打下基础。

var getContainerPos=function(container){//获取容器位置

var left=0;var top=0;while(container.offsetParent){                left+=container.offsetLeft;                top+=container.offsetTop;                container=container.offsetParent;

            }return [left,top];

        }

  该方法在很多时候都会需要用到,它循环获取有定位的父对象,累计出容器在页面的位置,返回结果。注意offsetParent是有定位的父对象,而不是单纯的父对象,没有的话就为window。该方法作为私有方法存在。

  var bindHandler = function() {//绑定容器的事件处理程序                var self = this;

this.container.onmousedown = function(eve) {                    add = true;                }

this.container.onmousemove = function(eve) {

                    window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); //取消浏览器图片选择                    if (add === true) {                        eve = eve || window.event;var pageX = eve.pageX || eve.clientX + document.documentElement.scrollLeft - document.documentElement.clientLeft;var pageY = eve.pageY || eve.clientY + document.documentElement.scrollTop - document.documentElement.clientTop;

var mouseX = pageX - self.containerPos[0] - self.centerLeft;var mouseY = -(pageY - self.containerPos[1]) + self.centerTop;

if (Math.abs(mouseX - preX) > self.ballMargin || Math.abs(mouseY - preY) > self.ballMargin) {                            self.addElem(mouseX, mouseY, 'ball/ball2.gif');                            preX = mouseX;                            preY = mouseY;                        }

                    }

                }this.container.onmouseup = function(eve) {                    add = false;                    preX = 0;                    preY = 0;

                }            };

  之后要实现鼠标拖动绘制,必须为鼠标按下,鼠标移动,鼠标松开都绑定事件处理程序。主要讲讲mousemove里的处理,首先由于在鼠标拖动时浏览器会默认选择图片(使图片上面有一层蓝色),所以我们通过removeAllRanges或empty方法取消选择,使我们的图片显示正常。之后,如果鼠标是按下状态(add句柄为true),则再判断上一次绘制小球的位置,如果位置大于我们之前设定的小球间隔,则绘制小球,并保存本次绘制小球的位置,为下次绘制所用。这样就使小球之间保持适当间隔而不至于太密。

    addElem:function(initX,initY,src){//添加小球                var newElem=new Image();                newElem.src=src;var self=this;                self.container.appendChild(newElem);

if(newElem.complete){;                    imgLoad.call(this,newElem,initX,initY)();                }else{                    newElem.οnlοad=imgLoad.call(this,newElem,initX,initY);                }

        },

  绘制小球的时候,会调用addElem方法,该方法生成小球对象,添加到文档。注意在IE8及以下版本,由于浏览器的缓存原因,只设置图片的onload的话,会不能执行onload处理程序(参考这里:http://apps.hi.baidu.com/share/detail/21688582),解决方法是通过complete方法先判断是否已再缓存中,如果是立刻执行处理程序,如果不在再绑定onload处理程序。

    var imgLoad=function(newElem,initX,initY){//图片加载完成事件处理程序            var self=this;return function(){

                initX<0?newElem.angle=Math.PI:newElem.angle=0;//小球被添加的位置,如果小球被添加在负区域,初始旋转角度为pai,否则为0                newElem._x=newElem._initX=initX;                newElem._y=newElem._initY=initY;                newElem._preSin=0;                                

                newElem._a=Math.abs(initX);                        //该小球旋转轨迹的a值                newElem._b=self.maxB;                            //该小球旋转轨迹的b值

//根据小球初始位置和上一个小球的位置,设置小球的层级                var preElem=self.arr[self.arr.length-1];if(preElem){var preZin=parseInt(preElem.style.zIndex);var preX=preElem._initX;                    preX>=initX?newElem.style.zIndex=preZin+1:newElem.style.zIndex=preZin-1;                }else{                    newElem.style.zIndex=zIn;                }

                newElem._initWidth=newElem.clientWidth;                newElem._initHeight=newElem.clientHeight;                self.arr.push(newElem);            }

        };

  现在看看图片加载完成的事件处理程序,每个小球根据其初始位置设置旋转的横轴长和初始角度,横轴长为小球x值的绝对值,竖轴长则全部一样,这里建议竖轴长设置为较小值,使小球运动轨迹的上下浮动不至于过大,保证视觉上的合理性。另外需要重点注意的是,每个小球添加时的zIndex值和该小球初始的x坐标值以及上一次添加的小球的x坐标值有关。如果添加的小球x值比上次小球添加时的x值小,则证明该小球更接近原点,因此视觉上应该更靠前,zIndex比上个小球的zIndex增加1,同理,若比上次小球x值大,则小球的zIndex值减少1.如果是第一个小球,则取默认zIndex(代码中的zIn)。

    run:(function(){

var updatePos=function(elem,angle,a,b,centerLeft,centerTop){//update每次小球位置 参数:小球对象,增加的角度,小球轨迹a值,小球轨迹b值                elem.angle+=angle;

                (elem.angle>2*Math.PI)&&(elem.angle-=2*Math.PI);//使小球角度介于0-pai之间,方便计算                elem._x=a*Math.cos(elem.angle);                //根据角度计算x值                elem._y=b*Math.sin(elem.angle)+elem._initY;//根据角度计算y值

                elem.style.left=elem._x+centerLeft-elem._initWidth/2+'px';                elem.style.top=-elem._y+centerTop-elem._initHeight/2+'px';//与小球上次的sin值比较,如果与本次sin值互为正负,则小球zIndex值取反(这样的目的是每次经过x轴时使小球的层级取反)                if(Math.sin(elem.angle)*elem._preSin<0){                                            elem.style.zIndex*=-1;                }                elem._preSin=Math.sin(elem.angle);    //记录小球每次的sin值,用于下次小球层级计算

            }

return function(){var self=this;this._id=window.setTimeout(function(){for(var i=0,len=self.arr.length;i<len;i++){//循环遍历更新所有小球的位置                        updatePos(self.arr[i],Math.PI/100,self.arr[i]._a,self.arr[i]._b,self.centerLeft,self.centerTop);                    }                    self._id=window.setTimeout(arguments.callee,50);                },50);

            };

  当一张张图片被添加之后,我们就要通过定时器,不断改变小球位置,使它们以不同的椭圆轨迹,以相同的角速度旋转。关于椭圆轨迹的计算问题,上一篇文章《椭圆旋转相册》中描述过,现在再简短描述一次。椭圆的标准方程为:),由于需要处理的是旋转,所以我们希望把对x,y的处理转换成对旋转角度的处理,因此x,y坐标可以表示为:x=a*cosα , y=b*sinα 。所以我们每次增加角速度α,就可以映射到xy直角坐标系中,实现旋转。还需要注意的是由于小球每次经过x轴,小球的层级就会相反(意思是原来小球A在B前面,经过x轴后,B在A前面),于是我们需要记录小球上一次的位置的sin值,并和本次的sin值比较,如果相乘小于0,则表示小球正在经过x轴,此时就取反小球的层级zIndex。run方法调用后,每次遍历数组,更新小球位置。

var r3d=new rotate3dDraw();

  最后是调用方法,不传值的话都取默认值。

所有源代码:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312" /><title>无标题文档</title><style type="text/css" rel="stylesheet">#container{width:600px; height:400px; background:black; position:relative; z-index:0; overflow:hidden;}#container img{ position:absolute; left:-9999px; top:-9999px;}</style></head>

<body>

<div id="container"></div></body><script>var rotate3dDraw = (function() {var r3Draw = function(id, options) {//options:椭圆轨迹的最大a和bthis.init(id, options);

        }        r3Draw.prototype = (function() {var zIn = 999; //默认初始的zIndex值var add = false; //是否开始添加小球var preX = 0; //上一个小球的X值var preY = 0; //上一个小球的Y值

var getContainerPos = function(container) {//获取容器位置

var left = 0;var top = 0;while (container.offsetParent) {                    left += container.offsetLeft;                    top += container.offsetTop;                    container = container.offsetParent;

                }return [left, top];

            }

var bindHandler = function() {//绑定容器的事件处理程序var self = this;

this.container.onmousedown = function(eve) {                    add = true;                }

this.container.onmousemove = function(eve) {

                    window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty(); //取消浏览器图片选择if (add === true) {                        eve = eve || window.event;var pageX = eve.pageX || eve.clientX + document.documentElement.scrollLeft - document.documentElement.clientLeft;var pageY = eve.pageY || eve.clientY + document.documentElement.scrollTop - document.documentElement.clientTop;

var mouseX = pageX - self.containerPos[0] - self.centerLeft;var mouseY = -(pageY - self.containerPos[1]) + self.centerTop;

if (Math.abs(mouseX - preX) > self.ballMargin || Math.abs(mouseY - preY) > self.ballMargin) {                            self.addElem(mouseX, mouseY, 'ball/ball2.gif');                            preX = mouseX;                            preY = mouseY;                        }

                    }

                }this.container.onmouseup = function(eve) {                    add = false;                    preX = 0;                    preY = 0;

                }            };

var imgLoad = function(newElem, initX, initY) {//图片加载完成事件处理程序var self = this;return function() {

                    initX < 0 ? newElem.angle = Math.PI : newElem.angle = 0; //小球被添加的位置,如果小球被添加在负区域,初始旋转角度为pai,否则为0                    newElem._x = newElem._initX = initX;                    newElem._y = newElem._initY = initY;                    newElem._preSin = 0;

                    newElem._a = Math.abs(initX);                     //该小球旋转轨迹的a值                    newElem._b = self.maxB;                         //该小球旋转轨迹的b值

//根据小球初始位置和上一个小球的位置,设置小球的层级var preElem = self.arr[self.arr.length - 1];if (preElem) {var preZin = parseInt(preElem.style.zIndex);var preX = preElem._initX;                        preX >= initX ? newElem.style.zIndex = preZin + 1 : newElem.style.zIndex = preZin - 1;                    }else {                        newElem.style.zIndex = zIn;                    }

                    newElem._initWidth = newElem.clientWidth;                    newElem._initHeight = newElem.clientHeight;                    self.arr.push(newElem);                }

            };

return {                init: function(id, options) {//初始化函数                     options = options || {};this.container = document.getElementById(id || 'container');this.centerLeft = this.container.clientWidth / 2; //原点的left值this.centerTop = this.container.clientHeight / 2; //原点的top值this.maxA = options.maxA || 300; //旋转椭圆轨迹的横半轴长this.maxB = options.maxB || 1; //旋转椭圆轨迹的竖半轴长this.ballMargin = options.ballMargin || 20; //绘画时小球与小球间间的距离this.arr = []; //保存画板上所有小球的数组this._id; //计时器Id

this.containerPos = getContainerPos(this.container); //容器位置

                    bindHandler.call(this);this.run();                },                addElem: function(initX, initY, src) {//添加小球var newElem = new Image();                    newElem.src = src;var self = this;                    self.container.appendChild(newElem);

if (newElem.complete) {                        ;                        imgLoad.call(this, newElem, initX, initY)();                    }else {                        newElem.onload = imgLoad.call(this, newElem, initX, initY);                    }

                },                run: (function() {

var updatePos = function(elem, angle, a, b, centerLeft, centerTop) {//update每次小球位置 参数:小球对象,增加的角度,小球轨迹a值,小球轨迹b值                        elem.angle += angle;

                        (elem.angle > 2 * Math.PI) && (elem.angle -= 2 * Math.PI); //使小球角度介于0-pai之间,方便计算                        elem._x = a * Math.cos(elem.angle);             //根据角度计算x值                        elem._y = b * Math.sin(elem.angle) + elem._initY; //根据角度计算y值

                        elem.style.left = elem._x + centerLeft - elem._initWidth / 2 + 'px';                        elem.style.top = -elem._y + centerTop - elem._initHeight / 2 + 'px';//与小球上次的sin值比较,如果与本次sin值互为正负,则小球zIndex值取反(这样的目的是每次经过x轴时使小球的层级取反)if (Math.sin(elem.angle) * elem._preSin < 0) {                            elem.style.zIndex *= -1;                        }                        elem._preSin = Math.sin(elem.angle); //记录小球每次的sin值,用于下次小球层级计算

                    }

return function() {var self = this;this._id = window.setTimeout(function() {for (var i = 0, len = self.arr.length; i < len; i++) {//循环遍历更新所有小球的位置                                updatePos(self.arr[i], Math.PI / 100, self.arr[i]._a, self.arr[i]._b, self.centerLeft, self.centerTop);                            }                            self._id = window.setTimeout(arguments.callee, 50);                        }, 50);

                    };

                })()

            }

        })();

return r3Draw;

    })();

var r3d=new rotate3dDraw();

</script></html>

欢迎转载,请标明出处:http://www.cnblogs.com/Cson/archive/2012/01/28/2330505.html


转载于:https://www.cnblogs.com/Cson/archive/2012/01/29/2330505.html

【CSON原创】javascript实现3D涂鸦效果相关推荐

  1. HTML+CSS+JavaScript制作3D云效果,叼炸天!可用鼠标控制方向!

    HTML+CSS+JavaScript制作3D云效果,叼炸天!可用鼠标控制方向! 作品介绍 1.网页作品简介方面 :3D云效果,叼炸天!可用鼠标控制方向! 2.网页作品编辑方面:此作品为学生个人主页网 ...

  2. javascript+CSS3 3D游戏/效果

    因为是学习Web前端的,对前端知识技术也十分关注.要做一个3D游戏时,我第一想到的就是HTML5+CSS3技术,可以这样说:Web的未来属于HTML5.再搭配javascript,基本可以能实现你所想 ...

  3. 【CSON原创】 图片滑动展开效果发布

    功能说明: 鼠标移动到不同图片上,该图片滑动展开,其它图片折叠. 支持IE 6 7 8 firefox chrome 效果预览:   实现原理: 当鼠标移动到某张图片,该图片以及该图片前的图片以相同速 ...

  4. 【CSON原创】HTML5字体动态粒子效果发布

    功能说明: 输入字体,按确定后,右侧画布出现字体的动态粒子效果. 效果预览: 输入显示内容: 实现分析: 之前看过hongru的事情没有想象中那么难--JX官网首页3D粒子效果,和当耐特砖家的HTML ...

  5. html语言玫瑰花代码,javascript+HTML5的canvas实现七夕情人节3D玫瑰花效果代码

    本文实例讲述了javascript+HTML5的canvas实现七夕情人节3D玫瑰花效果.分享给大家供大家参考.具体如下: 下面的玫瑰绘制用到了HTML 5的canvas,所以你的浏览器需要支持HTM ...

  6. [原创]自定义ViewPager实现3D画廊效果

    经常在群里看到有些开发者在提问:怎么实现3D画廊效果,没思路. 有人出谋划策,你重写onTouch,在里面去判断:或者你去重写滑动监听事件,滑动的时候去动态设置左右两边的图片的大小和缩放效果.可能你们 ...

  7. java3d翻转纪念相册_【CSON原创】 3D翻转相册发布

    功能说明: 通过鼠标上下左右的移动,翻转呈现相册.支持浏览器IE6 7 8(IE8下会比较卡,IE6 7则不会) firefox chrome 效果图: 实现原理: 根据鼠标的坐标,以及层与层之间缩放 ...

  8. css和js实现3d图片,JavaScript_纯JS实现旋转图片3D展示效果,CSS:style type=text/cssgt - phpStudy...

    纯JS实现旋转图片3D展示效果 CSS: #show{position:relative;margin:20px auto;width:800px;} .item{position:absolute; ...

  9. JS 实现3D立体效果的首页轮播图(瞬间让你的网站高大上,逼格满满)

    还是那句话,废话少说,直接上源代码:http://download.csdn.net/detail/cometwo/9387901 <html> <head> <titl ...

最新文章

  1. ORB_SLAM2代码阅读(1)——系统入口
  2. 跳至下一个断点_基金经理:DeFi将推动以太坊在下一个上涨周期中涨至9000美元...
  3. See the World 2015-6-10
  4. Visual Studio Code设置中文包/配置中文语言
  5. vue获取当前登陆用户信息
  6. java 事务的提出者_java中什么是事务
  7. antd 日期时间选择_Excel最全时间类函数总结,有必要收藏一下哦
  8. c语言新手的无奈,几个新手容易犯的错误
  9. 一个在线五笔的例子的代码,很不错,转载过来共享
  10. 考研程序设计30题系列(21-30题)
  11. 星际争霸2中一些你不曾注意到的搞笑细节
  12. 乐Pro3 乐视X720/乐视X722通刷官方线刷包_救砖包_解账户锁
  13. 理解posixpath.py in Python
  14. PDF转Word方法大盘点:看了这一篇,就不用再找转换技巧了
  15. 考研词汇(这些句子让你掌握7000个单词)
  16. POJ 3984-迷宫问题 (dfs)
  17. druid监控无法关闭(坑),及处理方式
  18. Arduino智能小车设计(四)
  19. MEDLINE与PubMed有什么区别?检索范围包含哪些?
  20. Fragment 中 commit already called

热门文章

  1. steam服务器错误修改器,吞食孔明传 v4.1二十四项修改器(感谢游侠会员peizhaochen原创制作)[支持STEAM/凤凰游戏平台/Wegame][更新4]...
  2. python正则表达式入门教程括号及字符
  3. 高学历就意味着高薪资?低学历转行3D建模,游戏建模成为首选
  4. 软件工程-人事管理系统项目(一)
  5. 我博士科研经历中的经验和教训——朱亮
  6. linux下远程桌面remmina安装,Linux-远程桌面连接工具remmina
  7. 李永乐复习全书线性代数 第六章 二次型
  8. 三不足成紧箍咒,河姆渡能否取到智慧城市这本真经
  9. 国家开放大学c语言题及答案,国家开放大学C语言试题及答案.docx
  10. 笔记|滴滴iOS客户端的架构,组件化,技术选型