After first try using canvastext, I use span elements to make mini love text animation.
(function(){function k(a,l,m){var n=0,o,p,q,r,s,t;while(!m)o=a[n++],o?o.c&&o["c"][0]=="p"&&(m=n):m=n-1;for(p=l;p<m;p++){q=a[p];if(q.t)q["t"]!="\n"?q.txt||(t=b[g](i[h]("span")),q.txt=t,t.textContent=q.t,t[f]("style","font:"+c+"px 'Helvetica';color:"+e)):q.txt||(q.txt=b[g](i[h]("br")));else if(j[q.c[0]](q)){r;for(s=m+1;s<a.length;s++){t=a[s];if(t.c&&t["c"][0]=="p"){r=s+1;break}}r||(r=a.length),m=r}}d=setTimeout(function(){k(a,l,m)},99)}var a="\n<p2><f90>I<p1><cred>♥<p1><c#000>J<p1>S<p2><f10>1k<p1>\nX<p1>D ",c,d,e="#000",f="setAttribute",g="appendChild",h="createElement",i=document,j={f:function(a){c=parseInt(a.c.slice(1))},c:function(a){e=a.c.slice(1)},p:function(a){if(!a.p){var b=(new Date).getTime();if(!a.i)a.i=b;else if(b>a.i+parseFloat(a.c.slice(1))*1e3)return a.p=!0}}};b[f]("style","text-align:center");var l=[],m="<",n="",o,p,q;for(p=0;p<a.length;p++)q=a[p],q==m||p==a.length-1?(m=="<"?(o={t:n},m=">"):(o={c:n},m="<"),n!=""&&l.push(o),n=""):q=="\n"?(n!=""&&l.push({t:n}),l.push({t:q}),n=""):n+=q;k(l,0)})();
KGZ1bmN0aW9uKCl7ZnVuY3Rpb24gayhhLGwsbSl7dmFyIG49MCxvLHAscSxyLHMsdDt3aGlsZSghbSlvPWFbbisrXSxvP28uYyYmb1siYyJdWzBdPT0icCImJihtPW4pOm09bi0xO2ZvcihwPWw7cDxtO3ArKyl7cT1hW3BdO2lmKHEudClxWyJ0Il0hPSJcbiI/cS50eHR8fCh0PWJbZ10oaVtoXSgic3BhbiIpKSxxLnR4dD10LHQudGV4dENvbnRlbnQ9cS50LHRbZl0oInN0eWxlIiwiZm9udDoiK2MrInB4ICdIZWx2ZXRpY2EnO2NvbG9yOiIrZSkpOnEudHh0fHwocS50eHQ9YltnXShpW2hdKCJiciIpKSk7ZWxzZSBpZihqW3EuY1swXV0ocSkpe3I7Zm9yKHM9bSsxO3M8YS5sZW5ndGg7cysrKXt0PWFbc107aWYodC5jJiZ0WyJjIl1bMF09PSJwIil7cj1zKzE7YnJlYWt9fXJ8fChyPWEubGVuZ3RoKSxtPXJ9fWQ9c2V0VGltZW91dChmdW5jdGlvbigpe2soYSxsLG0pfSw5OSl9dmFyIGE9IlxuPHAyPjxmOTA+STxwMT48Y3JlZD7imaU8cDE+PGMjMDAwPko8cDE+UzxwMj48ZjEwPjFrPHAxPlxuWDxwMT5EICIsYyxkLGU9IiMwMDAiLGY9InNldEF0dHJpYnV0ZSIsZz0iYXBwZW5kQ2hpbGQiLGg9ImNyZWF0ZUVsZW1lbnQiLGk9ZG9jdW1lbnQsaj17ZjpmdW5jdGlvbihhKXtjPXBhcnNlSW50KGEuYy5zbGljZSgxKSl9LGM6ZnVuY3Rpb24oYSl7ZT1hLmMuc2xpY2UoMSl9LHA6ZnVuY3Rpb24oYSl7aWYoIWEucCl7dmFyIGI9KG5ldyBEYXRlKS5nZXRUaW1lKCk7aWYoIWEuaSlhLmk9YjtlbHNlIGlmKGI+YS5pK3BhcnNlRmxvYXQoYS5jLnNsaWNlKDEpKSoxZTMpcmV0dXJuIGEucD0hMH19fTtiW2ZdKCJzdHlsZSIsInRleHQtYWxpZ246Y2VudGVyIik7dmFyIGw9W10sbT0iPCIsbj0iIixvLHAscTtmb3IocD0wO3A8YS5sZW5ndGg7cCsrKXE9YVtwXSxxPT1tfHxwPT1hLmxlbmd0aC0xPyhtPT0iPCI/KG89e3Q6bn0sbT0iPiIpOihvPXtjOm59LG09IjwiKSxuIT0iIiYmbC5wdXNoKG8pLG49IiIpOnE9PSJcbiI/KG4hPSIiJiZsLnB1c2goe3Q6bn0pLGwucHVzaCh7dDpxfSksbj0iIik6bis9cTtrKGwsMCl9KSgpOw==
(function(){
// Demo sting
var text = "\n<p2><f90>I<p1><cred>♥<p1><c#000>J<p1>S<p2><f10>1k<p1>\nX<p1>D ";
var fontsize;
var timeout;
var fontcolor = '#000';
// farming bytes
var sa = "setAttribute";
var ac = "appendChild";
var ce = "createElement";
var docu = document;
var cmds = {
// Font size
f:function(params) {
fontsize = parseInt(params.c.slice(1));
},
// Color
c:function(params) {
fontcolor = params.c.slice(1);
},
// Pause
p:function(token) {
if(!token.p) {
var time = new Date().getTime();
if(!token.i) {
token.i = time;
} else {
if(time>(token.i+parseFloat(token.c.slice(1))*1000)) {
return token.p = true;
}
}
}
}
}
function render(commands,ini,end) {
var index = 0, ele, i, node, new_end, ii, tmp;
// Find first pause
while(!end) {
ele = commands[index++];
if(ele) {
if((ele["c"])&&(ele["c"][0]=="p"))
end = index;
} else {
end = index-1;
}
}
for(i=ini;i<end;i++) {
node = commands[i];
if(node.t) {
if((node['t']!="\n")) {
if(!node.txt) {
tmp = b[ac](docu[ce]('span'));
node.txt = tmp
tmp.textContent = node.t;
tmp[sa]("style", "font:" + fontsize + "px 'Helvetica';color:"+fontcolor);
}
} else {
if(!node.txt) {
node.txt = b[ac](docu[ce]('br'));
}
}
} else {
if(cmds[node['c'][0]](node)) {
new_end;
for(ii=end+1;ii<commands.length;ii++) {
tmp = commands[ii];
if((tmp["c"])&&(tmp["c"][0]=="p")) {
new_end = ii+1;
break;
}
}
if(!new_end) {
new_end = commands.length;
}
end = new_end;
}
}
}
timeout = setTimeout(function() {
render(commands,ini,end);
},99);
}
// Align body
b[sa]("style", "text-align:center");
// Process string data
// function parse(text) 20 bytes
var ret = [], finding = "<", current = "",to_insert,i,ele;
for(i=0;i<text.length;i++) {
ele = text[i];
if(ele == finding || i==text.length-1) {
if(finding == "<") {
to_insert = {t:current};
finding = ">";
} else {
to_insert = {c:current};
finding = "<";
}
if(current != "") {
ret.push(to_insert);
}
current = "";
} else {
if(ele=="\n"){
if(current != "") {
ret.push({t:current});
}
ret.push({t:ele});
current = "";
} else {
current += ele;
}
}
}
// GO!!
render(ret,0);
})();