标题很难引人入胜,先放个效果图好了

如果图片吸引不了你,那我觉得也就没啥看的了。

demo链接: https://win7killer.github.io/demo_set/html_demo/canvas/can_demo/draw_roll_2.html

*************************************************

上次“雷达图效果”文章很荣幸,被“某天头条”抓数据抓去了,不开心的是demo链接等所有链接都干掉了~~~  blabla,连个名字都木有。

想看的再看下: http://www.cnblogs.com/ufex/p/6655336.html

*************************************************

创意来源

之前看到的gif效果,为了这个文章又去找了一下。貌似是ipad的app “Amaziograph”。看起来真的很爽,很美

配上我自己画的图先:

手残不会画画,各位见笑。(手机上浏览器画的哦)

DEMO讲解

1.效果分析

a.参考线坐标轴 -- 为了简单控制参考线显示隐藏,单独一个canvas来搞,也不用每次重绘

b.绘画主体 -- 绘画效果(canvas画线);对称效果(canvas旋转)

c.配置区 -- 简单dom

简单来看,很容易实现嘛

2.开搞

1> 坐标系统

  其实就是画几条线,但是要均分角度。一种方法是,计算出各个点,然后从中心点发散去画线;另一种是,一边旋转canvas,一边画圆心到统一坐标的线。由于绘画是需用到canvas旋转,所以这里统一使用旋转来处理。

  

那么,就需要先来处理canvas旋转

  1. function drawRotate(deg, fn, _ctx) {
  2. _ctx = _ctx || ctx
  3. _ctx.save();
  4. _ctx.translate(_ctx.canvas.width / 2, _ctx.canvas.height / 2);
  5. _ctx.rotate(deg);
  6. fn && fn(_ctx);
  7. _ctx.restore();
  8. }

当然,这个是我尝试多次之后写好的方法。

1、存储ctx状态到栈,

2、移动旋转点(canvas坐标原点)到canvas中心,

3、旋转指定角度,

4、执行绘制函数fn,

5、从栈里边取回ctx的状态(包含但不仅包含 fillStyle、strokenStyle、translate等等),这里主要处理的是translate,因为我们下次用到坐标会受影响,所以要让canva坐标原点回到原来的位置。

其实这里translate还是比较抽象比较绕的。。。可能我比较迟缓

然后,是绘制参考线坐标

  1. function baseLine() {
  2. ctx_role.clearRect(0, 0, ctx_role.canvas.width, ctx_role.canvas.height);
  3. var deg = 360 / pieace;
  4. console.log(deg);
  5. ctx_role.lineWidth = 1;
  6. ctx_role.strokeStyle = 'rgba(0,0,0,.5)';
  7. for (var i = 0, l = pieace; i < l; i++) {
  8. drawRotate(i * deg / 180 * Math.PI, function(ctx_role) {
  9. draw({
  10. bx: can_role.width / 2,
  11. by: can_role.width / 2,
  12. ex: can_role.width / 2 + can_role.width,
  13. ey: can_role.width / 2
  14. }, ctx_role);
  15. }, ctx_role);
  16. }
  17. }
  1. function draw(option, _ctx) {
  2. _ctx = _ctx || ctx;
  3. _ctx.beginPath();
  4. _ctx.moveTo(option.bx - _ctx.canvas.width / 2, option.by - _ctx.canvas.height / 2);
  5. _ctx.lineTo(option.ex - _ctx.canvas.width / 2, option.ey - _ctx.canvas.height / 2);
  6. _ctx.stroke();
  7. }

这样,就绘制完成参考线。

2>绘画主体

首先处理一般的画线。跟拖拽效果类似,在move过冲中一直画线链接两个点。对拖拽不了解的可以去了解下,直接上代码

  1. function bindPc() {
  2. can.onmousedown = function(e) {
  3. if (e.button != 0) {
  4. return false;
  5. }
  6.  
  7. var op = {};
  8. op.ex = op.bx = e.clientX - can.parentElement.offsetLeft + window.scrollX;
  9. op.ey = op.by = e.clientY - can.parentElement.offsetTop + window.scrollY;
  10. drawFn(op);
  11. document.onmousemove = function(e) {
  12. document.body.style.cursor = 'pointer';
  13. op.bx = op.ex;
  14. op.by = op.ey;
  15. op.ex = e.clientX - can.parentElement.offsetLeft + window.scrollX;
  16. op.ey = e.clientY - can.parentElement.offsetTop + window.scrollY;
  17. drawFn(op);
  18. };
  19. document.onmouseup = function() {
  20. document.body.style.cursor = 'default';
  21. document.onmouseup = document.onmousemove = null;
  22. };
  23. };
  24. }
  1. function drawFn(op) {
  2. var deg = Math.floor(360 / pieace);
  3. for (var i = 0, l = 360; i < l; i += deg) {
  4. drawRotate(i / 180 * Math.PI, function(ctx) {
  5. draw(op);
  6. });
  7. }
  8. }

需要注意,e.button 用来判断是鼠标哪个键,0是左键

这里又用到了前边的drawRotate 和 draw。

************************************

至此,应该可以画出对称的线条了。

以下就是锦上添花的事情了

************************************

增加移动端的绘制支持(惭愧,没怎么写过移动端,欢迎多指教)

  1. function bindWp() {
  2. can.addEventListener('touchstart', function(e) {
  3. op = can.op = {};
  4. op.ex = op.bx = e.touches[0].clientX - can.parentElement.offsetLeft + window.scrollX;
  5. op.ey = op.by = e.touches[0].clientY - can.parentElement.offsetTop + window.scrollY;
  6. drawFn(op);
  7. can.addEventListener('touchmove', touchMoveFn);
  8. can.addEventListener('touchend', touchEndFn);
  9. });
  10.  
  11. function touchEndFn() {
  12. document.body.style.cursor = 'default';
  13. can.removeEventListener('touchmove', touchMoveFn);
  14. can.removeEventListener('touchend', touchEndFn);
  15. }
  16.  
  17. function touchMoveFn(e) {
  18. op = can.op;
  19. document.body.style.cursor = 'pointer';
  20. op.bx = op.ex;
  21. op.by = op.ey;
  22. op.ex = e.touches[0].clientX - can.parentElement.offsetLeft + window.scrollX;
  23. op.ey = e.touches[0].clientY - can.parentElement.offsetTop + window.scrollY;
  24. drawFn(op);
  25. return false;
  26. }
  27. }

3>设置等

这里dom比较简单,就略过了。只说一项,下载canvas图片到本地

最简单的,右键保存图片到本地,但是你肯定会骂我傻,谁不知道这操作啊;那么就来稍微装X一下吧

线上代码

  1. function download() {
  2. var data = can.toDataURL('image/png', 0.8);
  3. var $a = document.createElement('a');
  4. $a.download = imgName.value || 'default.png';
  5. $a.target = '_blank';
  6. $a.href = data;
  7. $a.click();
  8. }

(写这个博客的时候,返现自己把这个方法写麻烦了,绕远了。/手动尴尬,这里直接改了)

关键点在于  a.download属性,这个是把文件下载到本地的关键哦,然后要把canvas转成base64(canvas.toDataUrl方法,不清楚的可以去去了解下,这里不再赘述)

******************************************************

最后,附上完整代码(可能会和上边的有点出如,还在调整)

  1. <!DOCTYPE html>
  2. <html lang="zh">
  3.  
  4. <head>
  5. <meta charset="UTF-8">
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  7. <meta http-equiv="X-UA-Compatible" content="ie=edge">
  8. <mtea author="win7killer@163.com"></mtea>
  9. <title>Document</title>
  10. <style>
  11. * {
  12. margin: 0;
  13. padding: 0;
  14. }
  15.  
  16. p {
  17. line-height: 15px;
  18. font-size: 12px;
  19. }
  20.  
  21. @media screen and (max-width: 768px) {
  22. .wrapper {
  23. width: auto;
  24. position: relative;
  25. overflow: hidden;
  26. }
  27. }
  28.  
  29. @media screen and (min-width: 769px) {
  30. .wrapper {
  31. width: 600px;
  32. height: 600px;
  33. margin: 100px auto 0;
  34. position: relative;
  35. overflow: hidden;
  36. }
  37. #panel_box {
  38. position: fixed;
  39. top: 20px;
  40. right: 20px;
  41. width: 200px;
  42. }
  43. }
  44.  
  45. canvas {
  46. background: #fafafa;
  47. display: block;
  48. }
  49.  
  50. #can_role {
  51. background: none;
  52. position: absolute;
  53. top: 0px;
  54. left: 0px;
  55. pointer-events: none;
  56. }
  57.  
  58. #panel_box {
  59. padding: 10px;
  60. margin-top: 10px;
  61. border: 1px solid rgba(10, 10, 10, .7);
  62. box-shadow: 10px 7px 10px #999;
  63. z-index: 100;
  64. }
  65.  
  66. input {
  67. width: 80px;
  68. margin-left: 20px;
  69. }
  70.  
  71. label {
  72. text-align: justify;
  73. }
  74. </style>
  75. </head>
  76.  
  77. <body>
  78. <div class="wrapper" id="wrapper">
  79. <canvas id="can_role"></canvas>
  80. <canvas id="can"></canvas>
  81. </div>
  82. <div id="panel_box">
  83. <p>
  84. <label>画笔颜色<input id="color_val" type="color" value="#0099ff"/></label>
  85. </p>
  86. <p>
  87. <label>画笔宽度<input type="number" id="line_width_val" min="1" max="20" value="2"/></label>
  88. </p>
  89. <p>
  90. <label>扇形份数<input type="number" id="pieaceNum" min="1" max="200" value="12"/></label>
  91. </p>
  92. <p>
  93. <label>参考线<input type="checkbox" id="onOff" checked="checked"/></label>
  94. </p>
  95. <p class="img_name_box">
  96. <label>图片名称<input type="text" id="imgName" placeholder="ex:test.png"></label>
  97. </p>
  98. <p>
  99. <a href="javascript:;" id="save_btn" target="">下载到本地</a>
  100. </p>
  101. </div>
  102. <script>
  103. var pieace = 6;
  104.  
  105. var ctx = can.getContext('2d');
  106. var ctx_role = can_role.getContext('2d');
  107.  
  108. can.width = can.height = can_role.width = can_role.height = window.screen.width > 768 ? 600 : window.screen.width;
  109.  
  110. ctx_role.lineJoin = ctx.lineJoin = "round";
  111. ctx_role.lineCap = ctx.lineCap = "round";
  112.  
  113. function drawFn(op) {
  114. var deg = Math.floor(360 / pieace);
  115. for (var i = 0, l = 360; i < l; i += deg) {
  116. drawRotate(i / 180 * Math.PI, function(ctx) {
  117. draw(op);
  118. });
  119. }
  120. }
  121.  
  122. function draw(option, _ctx) {
  123. _ctx = _ctx || ctx;
  124. _ctx.beginPath();
  125. _ctx.moveTo(option.bx - _ctx.canvas.width / 2, option.by - _ctx.canvas.height / 2);
  126. _ctx.lineTo(option.ex - _ctx.canvas.width / 2, option.ey - _ctx.canvas.height / 2);
  127. _ctx.stroke();
  128. }
  129.  
  130. function drawRotate(deg, fn, _ctx) {
  131. _ctx = _ctx || ctx
  132. _ctx.save();
  133. _ctx.translate(_ctx.canvas.width / 2, _ctx.canvas.height / 2);
  134. _ctx.rotate(deg);
  135. fn && fn(_ctx);
  136. _ctx.restore();
  137. }
  138.  
  139. function baseLine() {
  140. ctx_role.clearRect(0, 0, ctx_role.canvas.width, ctx_role.canvas.height);
  141. var deg = 360 / pieace;
  142. ctx_role.lineWidth = 1;
  143. ctx_role.strokeStyle = 'rgba(0,0,0,.5)';
  144. for (var i = 0, l = pieace; i < l; i++) {
  145. drawRotate(i * deg / 180 * Math.PI, function(ctx_role) {
  146. draw({
  147. bx: can_role.width / 2,
  148. by: can_role.width / 2,
  149. ex: can_role.width / 2 + can_role.width,
  150. ey: can_role.width / 2
  151. }, ctx_role);
  152. }, ctx_role);
  153. }
  154. }
  155.  
  156. function download() {
  157. var data = can.toDataURL('image/png', 0.8);
  158. var $a = document.createElement('a');
  159. $a.download = imgName.value || 'default.png';
  160. $a.target = '_blank';
  161. $a.href = data;
  162. $a.click();
  163. // if (typeof MouseEvent === 'function') {
  164. // var evt = new MouseEvent('click', {
  165. // view: window,
  166. // bubbles: true,
  167. // cancelable: false
  168. // });
  169. // $a.dispatchEvent(evt);
  170. // }
  171. }
  172.  
  173. function bindPc() {
  174. can.onmousedown = function(e) {
  175. if (e.button != 0) {
  176. return false;
  177. }
  178.  
  179. var op = {};
  180. op.ex = op.bx = e.clientX - can.parentElement.offsetLeft + window.scrollX;
  181. op.ey = op.by = e.clientY - can.parentElement.offsetTop + window.scrollY;
  182. drawFn(op);
  183. document.onmousemove = function(e) {
  184. document.body.style.cursor = 'pointer';
  185. op.bx = op.ex;
  186. op.by = op.ey;
  187. op.ex = e.clientX - can.parentElement.offsetLeft + window.scrollX;
  188. op.ey = e.clientY - can.parentElement.offsetTop + window.scrollY;
  189. drawFn(op);
  190. };
  191. document.onmouseup = function() {
  192. document.body.style.cursor = 'default';
  193. document.onmouseup = document.onmousemove = null;
  194. };
  195. };
  196. }
  197.  
  198. function bindWp() {
  199. can.addEventListener('touchstart', function(e) {
  200. op = can.op = {};
  201. op.ex = op.bx = e.touches[0].clientX - can.parentElement.offsetLeft + window.scrollX;
  202. op.ey = op.by = e.touches[0].clientY - can.parentElement.offsetTop + window.scrollY;
  203. drawFn(op);
  204. can.addEventListener('touchmove', touchMoveFn);
  205. can.addEventListener('touchend', touchEndFn);
  206. });
  207.  
  208. function touchEndFn() {
  209. document.body.style.cursor = 'default';
  210. can.removeEventListener('touchmove', touchMoveFn);
  211. can.removeEventListener('touchend', touchEndFn);
  212. }
  213.  
  214. function touchMoveFn(e) {
  215. op = can.op;
  216. document.body.style.cursor = 'pointer';
  217. op.bx = op.ex;
  218. op.by = op.ey;
  219. op.ex = e.touches[0].clientX - can.parentElement.offsetLeft + window.scrollX;
  220. op.ey = e.touches[0].clientY - can.parentElement.offsetTop + window.scrollY;
  221. drawFn(op);
  222. return false;
  223. }
  224. }
  225.  
  226. function bindSets() {
  227. color_val.onchange = function() {
  228. ctx.strokeStyle = color_val.value;
  229. }
  230.  
  231. line_width_val.onchange = function() {
  232. ctx.lineWidth = line_width_val.value;
  233. }
  234.  
  235. pieaceNum.onchange = function() {
  236. ctx.clearRect(0, 0, can.width, can.height);
  237. reset();
  238. }
  239.  
  240. onOff.onchange = function() {
  241. if (this.checked == true) {
  242. can_role.style.display = 'block';
  243. } else {
  244. can_role.style.display = 'none';
  245. }
  246. }
  247. }
  248.  
  249. function bind() {
  250. bindPc();
  251. bindWp();
  252. bindSets();
  253.  
  254. save_btn.onclick = download;
  255. }
  256.  
  257. function reset() {
  258. pieace = pieaceNum.value;
  259. ctx.strokeStyle = 'rgba(100,100,100,.7)';
  260. baseLine();
  261. ctx.lineWidth = line_width_val.value;
  262. ctx.strokeStyle = color_val.value;
  263. }
  264.  
  265. function init() {
  266. reset();
  267. bind();
  268. }
  269.  
  270. init();
  271. </script>
  272. </body>
  273.  
  274. </html>

  

**************偷偷留个名字,防抓  博客园-fe-bean***************

涉及姿势点总结  

1.canvas_translate

2.canvas_rotate

3.canvas_toDataUrl

4.a.download  &&  base64

其余的想起来再添加吧

最后,欢迎大家多提意见、交流,点赞转载那就更棒了。

再丢一张图

下期再见咯~~~

****************   少侠留步,能看到这里的,我要给你们一个奖励   ***************

这个demo是可以在移动端玩的,意味着有电容笔的亲,可以爽啊~(个别浏览器脑残会左右来回跑~~)

没有电容笔的亲,肯定是大多数,我们一样能玩啊!!!

叫你们快速做一款电容笔(当然没那么好用)

1.找一只木质铅笔

2.削出铅笔头

3.把铅笔头斜着磨平,如图

4.用磨平这一侧去电容屏上画(开始吧)

我上边那张图就是拿铅笔画的~~~

************************************

【canvas系列】canvas实现“ 简单的Amaziograph效果”--画对称图的更多相关文章

  1. 【canvas系列】canvas实现“ 简单的Amaziograph效果”--画对称图【强迫症福利】

    标题很难引人入胜,先放个效果图好了 如果图片吸引不了你,那我觉得也就没啥看的了. demo链接: https://win7killer.github.io/demo_set/html_demo/can ...

  2. canvas绘制简单的霓虹灯效果

    canvas简单动画分为三个步骤: 1.清除画布区域的内容: 2.重绘: 3.执行requestAnimationFrame(); 这个霓虹灯效果的demo,我没有用requestAnimationF ...

  3. 用canvas实现简单的下雪效果

    首先新建一个html文件,将body的背景设置为天空的那种深蓝色,并创建一个canvas,canvas的操作逻辑都放在snow.js中: <!DOCTYPE html> <head& ...

  4. 【canvas系列】用canvas实现一个colorpicker

    每个浏览器都有自己的特点,比如今天要做的colorpicker就是,一千个浏览器,一千个哈姆雷特,一千个colorpicker.今天canvas系列就用canvas做一个colorpicker. ** ...

  5. [js高手之路] html5 canvas系列教程 - 像素操作(反色,黑白,亮度,复古,蒙版,透明)

    接着上文[js高手之路] html5 canvas系列教程 - 状态详解(save与restore),相信大家都应该玩过美颜功能,而我们今天要讲的就是canvas强大的像素处理能力,通过像素处理,实现 ...

  6. [js高手之路] html5 canvas系列教程 - 状态详解(save与restore)

    本文内容与路径([js高手之路] html5 canvas系列教程 - 开始路径beginPath与关闭路径closePath详解)是canvas中比较重要的概念.掌握理解他们是做出复杂canvas动 ...

  7. 【canvas系列】用canvas实现一个colorpicker(类似PS的颜色选择器)

    每个浏览器都有自己的特点,比如今天要做的colorpicker就是,一千个浏览器,一千个哈姆雷特,一千个colorpicker.今天canvas系列就用canvas做一个colorpicker. ** ...

  8. [js高手之路] html5 canvas系列教程 - 文本样式(strokeText,fillText,measureText,textAlign,textBaseline)

    接着上文线条样式[js高手之路] html5 canvas系列教程 - 线条样式(lineWidth,lineCap,lineJoin,setLineDash)继续. canvas提供两种输出文本的方 ...

  9. [js高手之路] html5 canvas系列教程 - 线条样式(lineWidth,lineCap,lineJoin,setLineDash)

    上文,写完弧度与贝塞尔曲线[js高手之路] html5 canvas系列教程 - arcTo(弧度与二次,三次贝塞尔曲线以及在线工具),本文主要是关于线条的样式设置 lineWidth: 设置线条的宽 ...

随机推荐

  1. struts2.5框架使用通配符指定方法(常见错误)

    在学习struts框架时经常会使用到通配符调用方法,如下: <package name="shop" namespace="/" extends=&quo ...

  2. php表单修改数据

    (接前面写的) 第一个页面xiugai.php <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" ...

  3. request.RequestContextListener

    由于是使用spring mvc来做项目,因此脱离了HttpServletRequest作为参数,不能够直接使用request,要想使用request可以使用下面的方法: 在web点xml中配置一个监听 ...

  4. Java面试09|多线程

    1.假如有Thread1.Thread2.Thread3.Thread4四条线程分别统计C.D.E.F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现? 把相互独立的计算任 ...

  5. 1671: [Usaco2005 Dec]Knights of Ni 骑士

    1671: [Usaco2005 Dec]Knights of Ni 骑士 Time Limit: 5 Sec  Memory Limit: 64 MBSubmit: 254  Solved: 163 ...

  6. 快速上手UIBezierPath

    UIBezierPath主要用来绘制矢量图形,它是基于Core Graphics对CGPathRef数据类型和path绘图属性的一个封装,所以是需要图形上下文的(CGContextRef),所以一般U ...

  7. 用递归的方式处理数组 && 把递归方法方法定义到数组的原型上 (这是一次脑洞大开的神奇尝试)

    在 javascript 里,如果我们想用一个函数处理数组 (Array) 中的每个元素,那我们有很多种选择,最简单的当然就是用自带的 forEach 函数(低版本也可以使用 lodash 中的 fo ...

  8. Java对象序列化

    Java 1.1增添了一种有趣的特性,名为“对象序列化”(Object Serialization).它面向那些实现了Serializable接口的对象,可将它们转换成一系列字节,并可在以后完全恢复回 ...

  9. eclipse中以debug方式启动tomcat报错

    在eclipse中debug  Tomcat报错,错误如下: FATAL ERROR in native method: JDWP No transports initialized, jvmtiEr ...

  10. 【树莓派】iptables相关配置

    关于iptables的配置,参见官方资料:http://wiki.ubuntu.org.cn/IptablesHowTo 最好. 进入iptables # sudo iptables -L 列出目前的 ...