用一句很俗气的话概括这两天的情况就是:“最近很忙”,虽然手头上有不少很酷的HTML5和CSS3资源,但确实没时间将它们的实现过程写成教程分享给大家。今天刚完成了一个神秘的项目,空下来来博客园写点东西。今天给大家分享2个CSS3火焰文字特效,并且将实现的思路和核心代码写成教程分享给大家。
第一个是静态的火焰文字效果,先看看效果图:
看着图的效果很酷吧。
同时你也可以在这里查看火焰文字的DEMO演示。
下面是实现的源码,由于是静态的文字效果,所以代码相当比较简单。
HTML代码,就一个h1标签:
<h1 id="fire">HTML5 Tricks</h1>
然后是CSS3代码:
#fire{ text-align: center; margin: 100px auto; font-family: "Comic Sans MS"; font-size: 80px; color: white; text-shadow: 0 0 20px #fefcc9, 10px -10px 30px #feec85, -20px -20px 40px #ffae34, 20px -40px 50px #ec760c, -20px -60px 60px #cd4606, 0 -80px 70px #973716, 10px -90px 80px #451b0e; } body {background:black; }
这里简单说一下,主要是用了CSS3的text-shadow属性实现文字阴影,这里定义了7层的层叠阴影,用阶梯变化的颜色和一定的阴影半径模拟出火焰从里到外的颜色渐变。
第二个是带动画的火焰文字特效,说实话,和上一个相比,这个不怎么像火焰,但我还是称它火焰吧。先来看看效果图:
看看,是不是很大气。
要看动起来的效果,可以查看DEMO演示。
然后再分析一下源代码,由于涉及到CSS3动画,所以利用了JS代码动态改变CSS样式。
先是HTML代码,构造了一个div容器:
<div id="canvasContainer"></div>
下面是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.framesText = document.createElement("div"); this.framesText.style.color = ‘#00ffff‘; this.framesText.style.marginLeft = ‘3px‘; this.framesText.style.marginBottom = ‘3px‘; this.framesText.innerHTML = ‘<strong>FPS</strong>‘; this.container.appendChild(this.framesText); 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); this.context = this.canvas.getContext("2d"); this.context.fillStyle = ‘#101030‘; this.context.fillRect(0, 0, this.canvas.width, this.canvas.height ); this.contextImageData = this.context.getImageData(0, 0, this.canvas.width, this.canvas.height); setInterval( bargs( function( _this ) { _this.update(); return false; }, this ), 1000 ); }, getDisplayElement: function() { return this.container; }, tick: function() { this.frames++; }, update: function() { this.time = new Date().getTime(); this.fps = Math.round((this.frames * 1000 ) / (this.time - this.timePrev)); //.toPrecision(2); this.framesMin = Math.min(this.framesMin, this.fps); this.framesMax = Math.max(this.framesMax, this.fps); this.framesText.innerHTML = ‘<strong>‘ + this.fps + ‘ FPS</strong> (‘ + this.framesMin + ‘-‘ + this.framesMax + ‘)‘; this.contextImageData = this.context.getImageData(1, 0, this.canvas.width - 1, 30); this.context.putImageData(this.contextImageData, 0, 0); this.context.fillStyle = ‘#101030‘; this.context.fillRect(this.canvas.width - 1, 0, 1, 30); this.index = ( Math.floor(30 - Math.min(30, (this.fps / 60) * 30)) ); this.context.fillStyle = ‘#80ffff‘; this.context.fillRect(this.canvas.width - 1, this.index, 1, 1); this.context.fillStyle = ‘#00ffff‘; this.context.fillRect(this.canvas.width - 1, this.index + 1, 1, 30 - this.index); this.timePrev = this.time; this.frames = 0; } } // Hack by Spite 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 = {}; //return number between 1 and 0 Sakri.MathUtil.normalize = function(value, minimum, maximum){ return (value - minimum) / (maximum - minimum); }; //map normalized number to values Sakri.MathUtil.interpolate = function(normValue, minimum, maximum){ return minimum + (maximum - minimum) * normValue; }; //map a value from one set to another 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) { // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 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)); //has a dependency on Sakri.MathUtil (function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.Geom = {}; //================================================== //=====================::POINT::==================== //================================================== 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; }; //================================================== //===================::RECTANGLE::================== //================================================== 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)); /** * Created by sakri on 27-1-14. * has a dependecy on Sakri.Geom * has a dependecy on Sakri.BitmapUtil */ (function (window){ var Sakri = window.Sakri || {}; window.Sakri = window.Sakri || Sakri; Sakri.CanvasTextUtil = {}; //returns the biggest font size that best fits into given width 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(); //console.log("getFontSizeForWidth() 1 : ", copy.fontSize); context.font = copy.getFontString(); var textWidth = context.measureText(string).width; //SOME DISAGREEMENT WHETHER THIS SHOOULD BE WITH && or || 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; } } } //console.log("getFontSizeForWidth() 2 : ", copy.fontSize); return copy.fontSize; }; //========================================================================================= //==============::CANVAS TEXT PROPERTIES::==================================== //======================================================== 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); //======================== //general properties for demo set up //======================== 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 );} //avoid running resize scripts repeatedly if a browser window is being resized by dragging 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); } //======================== //Demo specific properties //======================== var animating = false; var particles = []; var numParticles = 4000; var currentText = "SAKRI"; 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; //fidget with these to manipulate effect var globalAlpha = .11; //amount of trails or tracers var xWind = 0; //all particles x is effected by this var threshold = 60; //if a pixels red component is less than this, return particle to it‘s original position var amountRed = 25; //amount of red added to a pixel occupied by a particle var amountGreen = 12; //amount of green added to a pixel occupied by a particle var amountBlue = 1; //amount of blue added to a pixel occupied by a particle var directionCount = 100; //number of random pre-calculated x and y directions function renderParticles(){ //fill renderBounds area with a transparent black, and render a nearly black text 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());//move randomly left or right xWind = Math.min(xWind, 1.5);//clamp to a maximum wind xWind = Math.max(xWind, -1.5);//clamp to a minimum wind 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 pixels red component is below set threshold, return particle to orgin if( data[index] < threshold){ particles[i] = particles[i+2]; particles[i+1] = particles[i+3]; } } context.putImageData(imageData, renderBounds.x, renderBounds.y); } var maxCharacters = 10; 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); } }
这两款CSS3火焰文字效果都还不错吧,如果你真的对HTML5感兴趣,可以点这里邮件订阅一下,注意需要登录邮箱确认订阅,这样有好的HTML5资源我会用邮件发送给你。
另外,如果你有微博,也可以用微博关注获取最新的HTML5资源和教程,我的新浪微博和腾讯微博。