个人主页搭建笔记(一)

一坨很杂的东西,涉及这两三天玩耍折腾学校提供的个人主页服务的各种

jQuery + velocity.js 配合 SVG 创建矢量图动画

SVG 是基于 XML 的被 W3C 所推荐的标准。一般情况下现代浏览器的 DOM 都会带有 SVG 的支持。在新版本的 Safari, Chrome, Firefox, IE 等浏览器中,我们不仅可以在 <canvas> 环境下利用 JavaScript 绘制位图图形,还可以在页面中创建或修改 <svg> 元素,并使用 SVG 语言绘制矢量图。

jQuery.animate() 是一个 JavaScript 动画引擎,利用它可以用于在浏览器的各个元素中实现动画。velocity.js 也是一个(可以和 jQuery 共同使用的)功能与 jQuery.animate() 相同的 JavaScript 动画引擎。它的效率表现要略高于 jQuery.animate() (可以参考 velocity.js 官网上的对比)。velocity.js 是支持在 <svg> 元素中实现动画的,使用方法和对其它元素的操作无异。

下面这个例子是用作主页欢迎信息的动画,其中通过 describeArc 函数将圆弧计算称为贝塞尔曲线,添加到网页的 <svg> 元素中之后再利用 $.velocity()rotateZ 创建动画。唯一要注意的是要在创建动画之前将贝塞尔曲线的变换中心 transform-origin 更改到圆心。

var arcNumber = 15,
      picRadius = 110,
      centerX = 150,
      centerY = 150;
  var svg = document.getElementById('onion');

  function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
      var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;

      return {
          x: centerX + (radius * Math.cos(angleInRadians)),
          y: centerY + (radius * Math.sin(angleInRadians))
      };
  }

  function describeArc(x, y, radius, startAngle, endAngle) {

      var start = polarToCartesian(x, y, radius, endAngle);
      var end = polarToCartesian(x, y, radius, startAngle);

      var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

      var d = [
          "M", start.x, start.y,
          "A", radius, radius, 0, arcSweep, 0, end.x, end.y
      ].join(" ");

      return d;
  }

  for (i = 0; i < arcNumber; i++) {
      var rotateDuration = 1000 + 4000 * Math.random(),
          arcAngle = 180 * Math.random();
      var arc = document.createElementNS("http://www.w3.org/2000/svg", 'path');
      var arcMirror = document.createElementNS("http://www.w3.org/2000/svg", 'path');
      arc.setAttribute("d", describeArc(centerX, centerY,
                                        picRadius * (i + 1) / arcNumber,
                                        0, arcAngle));
      arc.setAttribute("id", 'arc' + String(i));
      arc.setAttribute("transform-origin", '150 150');
      arcMirror.setAttribute("d", describeArc(centerX, centerY,
                                              picRadius * (i + 1) / arcNumber,
                                              180, arcAngle + 180));
      arcMirror.setAttribute("id", 'arcMirror' + String(i));
      arcMirror.setAttribute("transform-origin", '150 150');
      svg.appendChild(arc);
      svg.appendChild(arcMirror);
      $('#arc' + String(i)).velocity({ rotateZ: '+=360' }, {
          duration: rotateDuration,
          easing: 'linear',
          loop: true
      });
      $('#arcMirror' + String(i)).velocity({ rotateZ: '+=360' }, {
          duration: rotateDuration,
          easing: 'linear',
          loop: true
      });
  }
  

泊松圆盘分布的可视化

泊松圆盘分布 (Poisson Disc Distribution) 是一种点的随机分布。它在随机性的基础上尽可能地维持了点与点之间距离的均匀性。这种随机分布于我们视网膜上的视细胞的分布很相似,该分布可以用于图形采样。

具体算法如下,先指定第一个随机点,这个点就是泊松圆盘分布的第一个点。之后在已经产生的点中随机选取一个,在距离它 \(R < r < 2R\) 的范围内随机产生点,直到产生的那个随机点与其它任何点的距离都 \(> R\) 就将它保留为下一个随机点;或者连续产生的随机点达到某个阈值,使我们认为在我们选取的这个点附近无法继续产生新的点,就将它该点标记以后不再选取。重复以上步骤,直到所有点都被认定为无法产生新点,泊松圆盘的算法就算完成。

var interval = 20;
  var activeNodes = [],
      inactiveNodes = [],
      currentId = 0,
      delay = 0;
  var positionReference = $('#disc').position();
  var SVGobject = document.getElementById('disc');

  function module (vector) {
      return Math.sqrt(vector.x * vector.x + vector.y * vector.y);
  }

  function distance (pointA, pointB) {
      var deltaX = pointA.x - pointB.x;
      var deltaY = pointA.y - pointB.y;
      return module({ x: deltaX, y: deltaY });
  }

  function poissonDiscAppend (positionX, positionY) {
      activeNodes.push({ id: currentId, x: positionX, y: positionY });

      var node = document.createElementNS("http://www.w3.org/2000/svg", 'circle');
      node.setAttribute("r", 0);
      node.setAttribute("cx", positionX);
      node.setAttribute("cy", positionY);
      node.setAttribute("fill", '#FF0000');
      node.setAttribute("id", 'node' + String(currentId));
      SVGobject.appendChild(node);

      $('#node' + String(currentId++)).delay(delay).velocity({ r: 1.5 }, 300);
      delay += 10;
  }

  function poissonDiscGenerate (originX, originY, radius) {
      poissonDiscAppend(originX, originY);
      while (!!activeNodes[0]) {
          var index = Math.floor(Math.random() * activeNodes.length),
              valid = false;
          var nodeData = activeNodes[index];
          for (var i = 0; i < 20; i++) {
              var newNode = { x: 0, y: 0 },
                  pass = true;
              while (module(newNode) < radius ||
                     module(newNode) > 2 * radius) {
                  newNode.x = 4 * radius * Math.random() - 2 * radius;
                  newNode.y = 4 * radius * Math.random() - 2 * radius;
              }
              newNode.x += nodeData.x;
              newNode.y += nodeData.y;
              if (newNode.x < 0 || newNode.x > 600 ||
                  newNode.y < 0 || newNode.y > 400) {
                  pass = false;
              }
              for (var j = 0; j < activeNodes.length; j++) {
                  if (distance(activeNodes[j], newNode) < radius) {
                      pass = false;
                  }
              }
              for (var k = 0; k < inactiveNodes.length; k++) {
                  if (distance(inactiveNodes[k], newNode) < radius) {
                      pass = false;
                  }
              }
              if (pass) {
                  poissonDiscAppend(newNode.x, newNode.y);
                  valid = true;
                  break;
              }
          }
          if (!valid) {
              inactiveNodes.push((activeNodes.splice(index, 1))[0]);
              $('#node' + nodeData.id).delay(delay).velocity({ fill: "#000000" }, 100);
          }
      }
  }

  $('#disc').click(function (event) {
      if (!activeNodes[0] && !inactiveNodes[0]) {
          poissonDiscGenerate(event.pageX - positionReference.left,
                              event.pageY - positionReference.top, interval);
      }
  });
  

静态网页中获取 WordPress 博客上的信息

笔者希望在自己的个人主页上显示自己 WordPress 博客上的所有文章及其链接,怎么办?这个问题可以通过 WordPress 的插件 JSON API 解决。JSON API 可以让 WordPress 站点响应一些 JSON 请求,返回相应的 JSON 对象。同时,它也支持使用 callback 参数来解决跨域访问问题。这样,我们只需要实现写好对应的 JS 函数如 processWP ,然后通过下面这样的代码:

function processWP (WPObject) {
      /* everything you want to do*/
  }

  $('body').append($('<script>', { src: 'http://your.wordpress.homepage/\?json\=get_recent_posts\&callback\=processWP' }));
  

这样,当这段代码被加载后,前面一段代码会定义一个函数 processWP 用来将 WordPress 里的信息显示在当前页面上,后面一段代码则会在当前页面中插入一个 <script> 标签,通过这个标签是可以进行跨域访问的。这个 <script> 标签作为嵌入式 JavaScript 会让浏览器载入一个格式约为 precessWP(object) 的 WordPress 站点返回的文件(其中 object 是 WordPress 站点返回的具体 JSON 内容)。执行这段脚本时,WordPress 的 JSON 对象以对象字面量的形式作为参数调用函数 processWP ,实现内容嵌入。