直接上演示效果和代码, 不多讲
演示效果:
代码
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>火焰文字特效</title>
<link rel="stylesheet" href="css/style.css" media="screen" type="text/css" />
</head>
<body>
<div id="canvasContainer"></div>
<span id="textInputSpan">
请输入要显示的文字 :
<input id="textInput" maxlength="10" type="text" width="150" />
<button onclick="changeText()">运行</button>
</span>
<script src="js/index.js"></script>
<script>
currentText = 'Merkle・Jquery・Ru'
clearTimeoutsAndIntervals();
animating = false;
setTimeout(commitResize, 100);
</script>
</body>
</html>
style.css
html, body{
margin : 0px;
width : 100%;
height : 100%;
overflow: hidden;
background-color: #000000;
font-family: sans-serif;
}
#canvasContainer{
margin : 0px;
width : 100%;
height : 100%;
}
#textInputSpan{
position: absolute;
color: #FFFFFF;
font-family: sans-serif;
}
index.js
function Stats() {
this.init();
}
Stats.prototype =
{
init: function () {
this.frames = 0;
this.framesMin = 100;
this.framesMax = 0;
this.time = new Date().getTime();
this.timePrev = new Date().getTime();
this.container = document.createElement("div");
this.container.style.position = 'absolute';
this.container.style.fontFamily = 'Arial';
this.container.style.fontSize = '10px';
this.container.style.backgroundColor = '#000020';
this.container.style.opacity = '0.9';
this.container.style.width = '80px';
this.container.style.paddingTop = '2px';
this.canvas = document.createElement("canvas");
this.canvas.width = 74;
this.canvas.height = 30;
this.canvas.style.display = 'block';
this.canvas.style.marginLeft = '3px';
this.canvas.style.marginBottom = '3px';
this.container.appendChild(this.canvas);
},
getDisplayElement: function () {
return this.container;
},
tick: function () {
this.frames++;
},
}
function bargs(_fn) {
var args = [];
for (var n = 1; n < arguments.length; n++)
args.push(arguments[n]);
return function () { return _fn.apply(this, args); };
}
(function (window) {
var Sakri = window.Sakri || {};
window.Sakri = window.Sakri || Sakri;
Sakri.MathUtil = {};
Sakri.MathUtil.normalize = function (value, minimum, maximum) {
return (value - minimum) / (maximum - minimum);
};
Sakri.MathUtil.interpolate = function (normValue, minimum, maximum) {
return minimum + (maximum - minimum) * normValue;
};
Sakri.MathUtil.map = function (value, min1, max1, min2, max2) {
return Sakri.MathUtil.interpolate(Sakri.MathUtil.normalize(value, min1, max1), min2, max2);
};
Sakri.MathUtil.hexToRgb = function (hex) {
var shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex = hex.replace(shorthandRegex, function (m, r, g, b) {
return r + r + g + g + b + b;
});
var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
Sakri.MathUtil.getRandomNumberInRange = function (min, max) {
return min + Math.random() * (max - min);
};
Sakri.MathUtil.getRandomIntegerInRange = function (min, max) {
return Math.round(Sakri.MathUtil.getRandomNumberInRange(min, max));
};
}(window));
(function (window) {
var Sakri = window.Sakri || {};
window.Sakri = window.Sakri || Sakri;
Sakri.Geom = {};
Sakri.Geom.Point = function (x, y) {
this.x = isNaN(x) ? 0 : x;
this.y = isNaN(y) ? 0 : y;
};
Sakri.Geom.Point.prototype.clone = function () {
return new Sakri.Geom.Point(this.x, this.y);
};
Sakri.Geom.Point.prototype.update = function (x, y) {
this.x = isNaN(x) ? this.x : x;
this.y = isNaN(y) ? this.y : y;
};
Sakri.Geom.Rectangle = function (x, y, width, height) {
this.update(x, y, width, height);
};
Sakri.Geom.Rectangle.prototype.update = function (x, y, width, height) {
this.x = isNaN(x) ? 0 : x;
this.y = isNaN(y) ? 0 : y;
this.width = isNaN(width) ? 0 : width;
this.height = isNaN(height) ? 0 : height;
};
Sakri.Geom.Rectangle.prototype.getRight = function () {
return this.x + this.width;
};
Sakri.Geom.Rectangle.prototype.getBottom = function () {
return this.y + this.height;
};
Sakri.Geom.Rectangle.prototype.getCenter = function () {
return new Sakri.Geom.Point(this.getCenterX(), this.getCenterY());
};
Sakri.Geom.Rectangle.prototype.getCenterX = function () {
return this.x + this.width / 2;
};
Sakri.Geom.Rectangle.prototype.getCenterY = function () {
return this.y + this.height / 2;
};
Sakri.Geom.Rectangle.prototype.containsPoint = function (x, y) {
return x >= this.x && y >= this.y && x <= this.getRight() && y <= this.getBottom();
};
Sakri.Geom.Rectangle.prototype.clone = function () {
return new Sakri.Geom.Rectangle(this.x, this.y, this.width, this.height);
};
Sakri.Geom.Rectangle.prototype.toString = function () {
return "Rectangle{x:" + this.x + " , y:" + this.y + " , width:" + this.width + " , height:" + this.height + "}";
};
}(window));
(function (window) {
var Sakri = window.Sakri || {};
window.Sakri = window.Sakri || Sakri;
Sakri.CanvasTextUtil = {};
Sakri.CanvasTextUtil.getFontSizeForWidth = function (string, fontProps, width, canvas, fillStyle, maxFontSize) {
if (!canvas) {
var canvas = document.createElement("canvas");
}
if (!fillStyle) {
fillStyle = "#000000";
}
if (isNaN(maxFontSize)) {
maxFontSize = 500;
}
var context = canvas.getContext('2d');
context.font = fontProps.getFontString();
context.textBaseline = "top";
var copy = fontProps.clone();
context.font = copy.getFontString();
var textWidth = context.measureText(string).width;
if (textWidth < width) {
while (context.measureText(string).width < width) {
copy.fontSize++;
context.font = copy.getFontString();
if (copy.fontSize > maxFontSize) {
console.log("getFontSizeForWidth() max fontsize reached");
return null;
}
}
} else if (textWidth > width) {
while (context.measureText(string).width > width) {
copy.fontSize--;
context.font = copy.getFontString();
if (copy.fontSize < 0) {
console.log("getFontSizeForWidth() min fontsize reached");
return null;
}
}
}
return copy.fontSize;
};
Sakri.CanvasTextProperties = function (fontWeight, fontStyle, fontSize, fontFace) {
this.setFontWeight(fontWeight);
this.setFontStyle(fontStyle);
this.setFontSize(fontSize);
this.fontFace = fontFace ? fontFace : "sans-serif";
};
Sakri.CanvasTextProperties.NORMAL = "normal";
Sakri.CanvasTextProperties.BOLD = "bold";
Sakri.CanvasTextProperties.BOLDER = "bolder";
Sakri.CanvasTextProperties.LIGHTER = "lighter";
Sakri.CanvasTextProperties.ITALIC = "italic";
Sakri.CanvasTextProperties.OBLIQUE = "oblique";
Sakri.CanvasTextProperties.prototype.setFontWeight = function (fontWeight) {
switch (fontWeight) {
case Sakri.CanvasTextProperties.NORMAL:
case Sakri.CanvasTextProperties.BOLD:
case Sakri.CanvasTextProperties.BOLDER:
case Sakri.CanvasTextProperties.LIGHTER:
this.fontWeight = fontWeight;
break;
default:
this.fontWeight = Sakri.CanvasTextProperties.NORMAL;
}
};
Sakri.CanvasTextProperties.prototype.setFontStyle = function (fontStyle) {
switch (fontStyle) {
case Sakri.CanvasTextProperties.NORMAL:
case Sakri.CanvasTextProperties.ITALIC:
case Sakri.CanvasTextProperties.OBLIQUE:
this.fontStyle = fontStyle;
break;
default:
this.fontStyle = Sakri.CanvasTextProperties.NORMAL;
}
};
Sakri.CanvasTextProperties.prototype.setFontSize = function (fontSize) {
if (fontSize && fontSize.indexOf && fontSize.indexOf("px") > -1) {
var size = fontSize.split("px")[0];
fontProperites.fontSize = isNaN(size) ? 24 : size;//24 is just an arbitrary number
return;
}
this.fontSize = isNaN(fontSize) ? 24 : fontSize;//24 is just an arbitrary number
};
Sakri.CanvasTextProperties.prototype.clone = function () {
return new Sakri.CanvasTextProperties(this.fontWeight, this.fontStyle, this.fontSize, this.fontFace);
};
Sakri.CanvasTextProperties.prototype.getFontString = function () {
return this.fontWeight + " " + this.fontStyle + " " + this.fontSize + "px " + this.fontFace;
};
}(window));
window.requestAnimationFrame =
window.__requestAnimationFrame ||
window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
(function () {
return function (callback, element) {
var lastTime = element.__lastTime;
if (lastTime === undefined) {
lastTime = 0;
}
var currTime = Date.now();
var timeToCall = Math.max(1, 33 - (currTime - lastTime));
window.setTimeout(callback, timeToCall);
element.__lastTime = currTime + timeToCall;
};
})();
var readyStateCheckInterval = setInterval(function () {
if (document.readyState === "complete") {
clearInterval(readyStateCheckInterval);
init();
}
}, 10);
var canvas;
var context;
var canvasContainer;
var htmlBounds;
var bounds;
var minimumStageWidth = 250;
var minimumStageHeight = 250;
var maxStageWidth = 1000;
var maxStageHeight = 600;
var resizeTimeoutId = -1;
var stats;
function init() {
canvasContainer = document.getElementById("canvasContainer");
window.onresize = resizeHandler;
stats = new Stats();
canvasContainer.appendChild(stats.getDisplayElement());
commitResize();
}
function getWidth(element) { return Math.max(element.scrollWidth, element.offsetWidth, element.clientWidth); }
function getHeight(element) { return Math.max(element.scrollHeight, element.offsetHeight, element.clientHeight); }
function resizeHandler() {
context.clearRect(0, 0, canvas.width, canvas.height);
clearTimeout(resizeTimeoutId);
clearTimeoutsAndIntervals();
resizeTimeoutId = setTimeout(commitResize, 300);
}
function commitResize() {
if (canvas) {
canvasContainer.removeChild(canvas);
}
canvas = document.createElement('canvas');
canvas.style.position = "absolute";
context = canvas.getContext("2d");
canvasContainer.appendChild(canvas);
htmlBounds = new Sakri.Geom.Rectangle(0, 0, getWidth(canvasContainer), getHeight(canvasContainer));
if (htmlBounds.width >= maxStageWidth) {
canvas.width = maxStageWidth;
canvas.style.left = htmlBounds.getCenterX() - (maxStageWidth / 2) + "px";
} else {
canvas.width = htmlBounds.width;
canvas.style.left = "0px";
}
if (htmlBounds.height > maxStageHeight) {
canvas.height = maxStageHeight;
canvas.style.top = htmlBounds.getCenterY() - (maxStageHeight / 2) + "px";
} else {
canvas.height = htmlBounds.height;
canvas.style.top = "0px";
}
bounds = new Sakri.Geom.Rectangle(0, 0, canvas.width, canvas.height);
context.clearRect(0, 0, canvas.width, canvas.height);
if (bounds.width < minimumStageWidth || bounds.height < minimumStageHeight) {
stageTooSmallHandler();
return;
}
var textInputSpan = document.getElementById("textInputSpan");
textInputSpan.style.top = htmlBounds.getCenterY() + (bounds.height / 2) + 20 + "px";
textInputSpan.style.left = (htmlBounds.getCenterX() - getWidth(textInputSpan) / 2) + "px";
startDemo();
}
function stageTooSmallHandler() {
var warning = "Sorry, bigger screen required :(";
context.font = "bold normal 24px sans-serif";
context.fillText(warning, bounds.getCenterX() - context.measureText(warning).width / 2, bounds.getCenterY() - 12);
}
var animating = false;
var particles = [];
var numParticles = 4000;
var currentText = "Merkle・Jquery・Ru";
var fontRect;
var fontProperties = new Sakri.CanvasTextProperties(Sakri.CanvasTextProperties.BOLD, null, 100);
var animator;
var particleSource = new Sakri.Geom.Point();;
var particleSourceStart = new Sakri.Geom.Point();
var particleSourceTarget = new Sakri.Geom.Point();
var redParticles = ["#fe7a51", "#fdd039", "#fd3141"];
var greenParticles = ["#dbffa6", "#fcf8fd", "#99de5e"];
var pinkParticles = ["#fef4f7", "#f2a0c0", "#fb3c78"];
var yellowParticles = ["#fdfbd5", "#fff124", "#f4990e"];
var blueParticles = ["#9ca2df", "#222a6d", "#333b8d"];
var particleColorSets = [redParticles, greenParticles, pinkParticles, yellowParticles, blueParticles];
var particleColorIndex = 0;
var renderParticleFunction;
var renderBounds;
var particleCountOptions = [2000, 4000, 6000, 8000, 10000, 15000, 20000];
var pixelParticleCountOptions = [10000, 40000, 60000, 80000, 100000, 150000];
function clearTimeoutsAndIntervals() {
animating = false;
}
function startDemo() {
fontRect = new Sakri.Geom.Rectangle(Math.floor(bounds.x + bounds.width * .2), 0, Math.floor(bounds.width - bounds.width * .4), bounds.height);
fontProperties.fontSize = 100;
fontProperties.fontSize = Sakri.CanvasTextUtil.getFontSizeForWidth(currentText, fontProperties, fontRect.width, canvas);
fontRect.y = Math.floor(bounds.getCenterY() - fontProperties.fontSize / 2);
fontRect.height = fontProperties.fontSize;
renderBounds = fontRect.clone();
renderBounds.x -= Math.floor(canvas.width * .1);
renderBounds.width += Math.floor(canvas.width * .2);
renderBounds.y -= Math.floor(fontProperties.fontSize * .5);
renderBounds.height += Math.floor(fontProperties.fontSize * .6);
context.font = fontProperties.getFontString();
createParticles();
context.globalAlpha = globalAlpha;
animating = true;
loop();
}
function loop() {
if (!animating) {
return;
}
stats.tick();
renderParticles();
window.requestAnimationFrame(loop, canvas);
}
function createParticles() {
context.clearRect(0, 0, canvas.width, canvas.height);
context.fillText(currentText, fontRect.x, fontRect.y);
var imageData = context.getImageData(fontRect.x, fontRect.y, fontRect.width, fontRect.height);
var data = imageData.data;
var length = data.length;
var rowWidth = fontRect.width * 4;
var i, y, x;
particles = [];
for (i = 0; i < length; i += 4) {
if (data[i + 3] > 0) {
y = Math.floor(i / rowWidth);
x = fontRect.x + (i - y * rowWidth) / 4;
particles.push(x);//x
particles.push(fontRect.y + y);//y
particles.push(x);//xOrigin
particles.push(fontRect.y + y);//yOrigin
}
}
//console.log(particles.length);
context.clearRect(0, 0, canvas.width, canvas.height);
//pre calculate random numbers used for particle movement
xDirections = [];
yDirections = [];
for (i = 0; i < directionCount; i++) {
xDirections[i] = -7 + Math.random() * 14;
yDirections[i] = Math.random() * - 5;
}
}
var xDirections, yDirections;
var globalAlpha = .11;
var xWind = 0;
var threshold = 60;
var amountRed = 25;
var amountGreen = 12;
var amountBlue = 1;
var directionCount = 100;
function renderParticles() {
context.fillStyle = "#000000";
context.fillRect(renderBounds.x, renderBounds.y, renderBounds.width, renderBounds.height);
context.fillStyle = "#010000";
context.fillText(currentText, fontRect.x, fontRect.y);
var randomRed = amountRed - 5 + Math.random() * 10;
var randomGreen = amountGreen - 2 + Math.random() * 4;
var imageData = context.getImageData(renderBounds.x, renderBounds.y, renderBounds.width, renderBounds.height);
var data = imageData.data;
var rowWidth = imageData.width * 4;
var index, i, length = particles.length;
var d = Math.floor(Math.random() * 30);
xWind += (-.5 + Math.random());
xWind = Math.min(xWind, 1.5);
xWind = Math.max(xWind, -1.5);
for (i = 0; i < length; i += 4, d++) {
particles[i] += (xDirections[d % directionCount] + xWind);
particles[i + 1] += yDirections[d % directionCount];
index = Math.round(particles[i] - renderBounds.x) * 4 + Math.round(particles[i + 1] - renderBounds.y) * rowWidth;
data[index] += randomRed;
data[index + 1] += randomGreen;
data[index + 2] += amountBlue;
if (data[index] < threshold) {
particles[i] = particles[i + 2];
particles[i + 1] = particles[i + 3];
}
}
context.putImageData(imageData, renderBounds.x, renderBounds.y);
}
var maxCharacters = 100;
function changeText() {
var textInput = document.getElementById("textInput");
if (textInput.value && textInput.text != "") {
if (textInput.value.length > maxCharacters) {
alert("Sorry, there is only room for " + maxCharacters + " characters. Try a shorter name.");
return;
}
if (textInput.value.indexOf(" ") > -1) {
alert("Sorry, no support for spaces right now :(");
return;
}
currentText = textInput.value;
clearTimeoutsAndIntervals();
animating = false;
setTimeout(commitResize, 100);
}
}
function changeSettings() {
clearTimeoutsAndIntervals();
animating = false;
setTimeout(commitResize, 100);
}
function setParticleNumberOptions(values) {
var selector = document.getElementById("particlesSelect");
if (selector.options.length > 0 && parseInt(selector.options[0].value) == values[0]) {
return;
}
while (selector.options.length) {
selector.remove(selector.options.length - 1);
}
for (var i = 0; i < values.length; i++) {
selector.options[i] = new Option(values[i], values[i], i == 0, i == 0);
}
}