SOURCE

console 命令行工具 X clear

                    
>
console
/**
 * 文字流类
 * @param {*} args 
 * args = {
 *  canvas: Canvas,
 *  ctx: getContext('2d'),
 *  points [{ // 源数据点
 *    text,
 *    x,
 *    y
 *  }],
 *  fadeInStyle: String, // 淡入样式
 *  fadeOutStyle: String, // 淡出样式
 *  fontStyle: String // 文字样式
 * }
 * 数据点完整结构
 * { 
 *   text: String,  // 文字 
 *   x: Number,     // x坐标
 *   y: Number,     // y坐标
 *   ys: Number,    // 初始y坐标
 *   lh: Number,    // 行高
 *   th: Number,    // 文字高
 *   v: Number      // 速度
 * }
 */
var TextFlow = function(args) {
  this.stop = false;
  this.canvas = args.canvas;
  this.ctx = args.ctx;
  this.rawPoints = args.points;
  this.data = new TextFlowData();
  this.points = this.data.getPoints.bind(this.data);
  this.fadeInStyle = args.fadeInStyle || "rgba(0,0,0, 0)";
  this.fadeOutStyle = args.fadeOutStyle || "rgba(0,0,0, 1)";

  this.ctx.font = args.fontStyle || '20px normal';
  this.addPoints(this.rawPoints);
}
// 增加显示点
TextFlow.prototype.addPoints = function(rawPoints) {
  this._preprocessingPoints(rawPoints);
  this.data.addPoints(rawPoints);
  this._checkPointsAndStop();
}
// 启动动画
TextFlow.prototype.start = function() {
  window.requestAnimationFrame(this._animate.bind(this));
}
TextFlow.prototype.stopAnimate = function() {
  this.stop = true;
}
// 
TextFlow.prototype._checkPointsAndStop = function() {
  var points = this.points();
  if (points.length === 0) {
    this.stop = true;
  } else {
    if (this.stop) {
      // 重新开始
      this.start();
    }
    this.stop = false;
  }
}
// 源数据点预处理,计算lh/th
TextFlow.prototype._preprocessingPoints = function(rawPoints) {
  var lineHeight = (this.canvas && parseInt(window.getComputedStyle(this.canvas).lineHeight)) || parseInt(window.getComputedStyle(document.body).lineHeight);
  
  for(var i = 0; i < rawPoints.length; i++) {
    var str = rawPoints[i].text;
    var text = str.split('');
    var textMetrics = this.ctx.measureText(str);
    var textWidth = textMetrics.width / text.length;
    var textHeight = text.length * lineHeight;

    rawPoints[i].lh = lineHeight;
    rawPoints[i].th = textHeight;
  }
}
// 动画
TextFlow.prototype._animate = function(item) {
  if (!this.stop) {
    this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
    this._draw();
    this._update();
  	window.requestAnimationFrame(this._animate.bind(this));;
  } 
}
// 画图
TextFlow.prototype._draw = function() {
  // console.log('_draw called', this.points.length);
  var points = this.points();
  this.ctx.save();
  for(var i = 0; i < points.length; i++) {
    var point = points[i];
    // console.info(i, point);
    this._fillVerticalText(point.text, point.x, point.y, point.ys, point.lh, point.th);
  }
  this.ctx.restore();
}
// 更新图的状态
TextFlow.prototype._update = function() {
  var points = this.points();  
  for(var i = points.length - 1; i >= 0; i--) {
    var point = points[i];
    point.y = point.y - point.v;
    // 位移三倍高度时颜色已经透明(淡出),删除元素
    if (point.y < point.ys - point.th * 3) {
      this.data.reducePointByIndex(i);
    }
  }
  this._checkPointsAndStop();
}
// 画文字
TextFlow.prototype._fillVerticalText = function(str, x, y, ys, lh, th) {
  var text = str.split('');
  if (y < ys - th) {
    // console.log('fadeOut')
    this.ctx.fillStyle = this._createColor(x, ys - th, x, ys - th * 2, true);    
  } else {
  	this.ctx.fillStyle = this._createColor(x, ys, x, ys - th);    
  }
  for (var i = 0; i < text.length; i++) {
    y = Math.ceil(y + lh);
    //console.log('text=' + text[i] + ', x=' + x + ', y=' + y);
    this.ctx.fillText(text[i], x, y);
  }
}
// 创建颜色
TextFlow.prototype._createColor = function(x1, y1, x2, y2, fadeOut) {
  // return 'black';
  var gradient = this.ctx.createLinearGradient(x1, y1, x2, y2);
  if (fadeOut) {
    gradient.addColorStop(0, this.fadeOutStyle);
		gradient.addColorStop(1, this.fadeInStyle);
  } else {
    gradient.addColorStop(0, this.fadeInStyle);
		gradient.addColorStop(1, this.fadeOutStyle);
  }
  return gradient;
}
/**
 * 文字流数据类
 * @param {*} args 
 * args: {
 *   maxPoint: Number,        // 显示点最大值
 *   maxPointsPool: Number,   // 备用池最大值
 * }
 */
var TextFlowData = function(args) {
  this.points = [];
  this.pointsPool = [];
  this.maxPoint = (args && args.maxPoint) || 100;
  this.maxPointsPool = (args && args.maxPointsPool) || 500;
}
// 获取显示点
TextFlowData.prototype.getPoints = function() {
  return this.points;
}
// 增加显示点
TextFlowData.prototype.addPoints = function(rawPoints) {
  // console.log('TextFlow addPoints called');
  this._addPointPool(rawPoints);
  this._shiftPointPool();
}
// 删除显示点
TextFlowData.prototype.reducePointByIndex = function(i) {
  this.points.splice(i, 1);
}
// 创建点
TextFlowData.prototype._createPoints = function(rawPoints) {
  var resPoints = [];
  for(var i = 0; i < rawPoints.length; i++) {
    var point = rawPoints[i],
      aPoint = {
        text: point.text,
        x: point.x,
        y: point.y,
        ys: point.y,
        lh: point.lh,
        th: point.th,
        v: Math.floor(Math.random() * 3) + 1     
      };
    resPoints.push(aPoint);
  }
  return resPoints;
}
// 填充备用池
TextFlowData.prototype._addPointPool = function(rawPoints) {
  if (rawPoints.length > this.maxPointsPool) {
    var reduceCount = rawPoints.length - this.maxPointsPool;
    rawPoints.splice(0, reduceCount);
    this.pointsPool.splice(0);
  } else if (rawPoints.length + this.pointsPool.length >= this.maxPointsPool) {
    var addCount = this.maxPointsPool - rawPoints.length;   
    this.pointsPool.splice(0, addCount);
  }
  var addPoints = this._createPoints(rawPoints);
  this.pointsPool = this.pointsPool.concat(addPoints);
}
// 推出备用池进入显示
TextFlowData.prototype._shiftPointPool = function() {
  var addCount = this.maxPoint - this.points.length;
  if (addCount <= 0) return;
  this.points = this.points.concat(this.pointsPool.splice(0, addCount));
}

// 运行
var canvas = document.getElementById('myCanvas'),
ctx = canvas.getContext('2d'),
points = [{
  text: '123456',
  x: 100,
  y: 500
}];

initPoints();

function initPoints() {
  var i = 100;
  while(i > 0) {
    initPoint();
    i--;
  }
}

function initPoint() {
  var maxCount = 999999999;
  maxX = canvas.width - 30,
  maxY = canvas.height - 30,
  point = {
    text: Math.floor(Math.random() * maxCount) + '',
    x: Math.floor(Math.random() * maxX),
    y: Math.floor(Math.random() * maxY)
  };
  points.push(point);
}

console.info('points', points);

var textflow = new TextFlow({
  canvas: canvas,
  ctx: ctx,
  points: points,
  fadeInStyle: 'rgba(64, 64, 64, 0)',
  fadeOutStyle: 'rgba(64, 64, 64, 1)'
})

textflow.start();

setInterval(function() {
  points = [];
  initPoints();
  textflow.addPoints(points);
}, 5000)
<canvas id="myCanvas" width="500" height="400"></canvas>
canvas{
  background-color: #eee;
  line-height: 1.2;
}