UPDATED: 07/07/16 - I made it so each word that makes up an inked out message is a separate canvas. This makes the content flow like real words do - text wrapping, etc.
A Pen by Darryl Huffman on CodePen.
<div class="flex"> | |
<div class="content"> | |
<h2>Invisible Ink</h2> | |
<div class="text"> | |
<p class="project_text" style="margin-top: 0px; opacity: 1;">Invisible Ink was a simple <em>library</em> that will take specified text and "ink" it out, making it unreadable until hovered upon. It has quite a few applicable uses, like for hiding <span class="ink">spoilers</span> and <span class="ink">security words</span>. It was inspired by one of iOS 10's new features for iMessages.</p> | |
</div> | |
</div> | |
</div> |
UPDATED: 07/07/16 - I made it so each word that makes up an inked out message is a separate canvas. This makes the content flow like real words do - text wrapping, etc.
A Pen by Darryl Huffman on CodePen.
/* | |
Feel free to use this in your projects! It'd be cool if you'd tell me though, to see what you do with it! | |
The font, color, size, and everything will be carried over accurately. | |
This is an attempt to re-create the invisible ink texts for iOS, and it turns out that it might work on websites in general... Swipe to see current password? Security Question answers? Who knows. | |
Usage is simple, the JS file handles CSS and all JS. | |
Here's the markup: | |
<span class="ink">ANYTHING</span> | |
So far there is no support for line breaks. | |
*/ | |
(function(){ | |
function wrapWords(el) { | |
'use strict'; | |
$(el).filter(':not(script)').contents().each(function () { | |
if (this.nodeType === Node.ELEMENT_NODE) { | |
wrapWords(this); | |
} else if (this.nodeType === Node.TEXT_NODE && !this.nodeValue.match(/^\s+$/m)) { | |
$(this).replaceWith($.map(this.nodeValue.split(/(\S+)/), function (w) { | |
return w.match(/^\s*$/) ? document.createTextNode(w) : $('<span>', {class: 'word', text: w}).get(); | |
})); | |
} | |
}); | |
}; | |
$('.ink').each(function(){ | |
$(this).html(wrapWords(this)); | |
$(this).find('.word').each(function(){ | |
inkout($(this)); | |
$(this).addClass('initialized'); | |
}); | |
}); | |
$('body').on('DOMNodeInserted', function(e) { | |
if ($(e.target).is('.ink') && $(e.target).hasClass('initialized') == false) { | |
setTimeout(function(){ | |
$(e.target).html(wrapWords(e.target)); | |
$(e.target).find('.word').each(function(){ | |
if(!$(this).hasClass('initialized')){ | |
inkout($(this)); | |
$(this).addClass('initialized'); | |
} | |
}); | |
$(e.target).addClass('initialized'); | |
}, 100); | |
} | |
}); | |
function css( element, property ) { | |
return window.getComputedStyle( element, null ).getPropertyValue( property ); | |
} | |
function inkout(element){ | |
element.parent().css('position','relative'); | |
var startTime = new Date().getTime(); | |
var currentTime = startTime / 1000; | |
var font = element.css('font-size') +' '+ element.css('font-family'); | |
var color = element.css('color'); | |
var text = element.html(); | |
var particles = []; | |
var hoverArray = []; | |
var cw = element.width(), | |
ch = element.height(); | |
element.html(''); | |
var canvas = $('<canvas/>').attr({width: cw, height: ch}).css({display: 'inline-block','vertical-align': 'text-bottom'}).appendTo(element), | |
context = canvas.get(0).getContext("2d"); | |
function drawText(){ | |
context.clearRect(0,0,cw,ch); | |
context.fillStyle = color; | |
context.clearRect(0,0,cw,ch); | |
context.font = font; | |
context.textAlign = "center"; | |
context.fillText(text,cw/2, ch - (ch/5)); | |
} | |
$(window).resize(function(){ | |
element.html(text); | |
font = element.css('font-size') +' '+ element.css('font-family'); | |
particles = []; | |
cw = element.width(), | |
ch = element.height(); | |
element.html(''); | |
canvas = $('<canvas/>').attr({width: cw, height: ch}).css({display: 'inline-block','vertical-align': 'top'}).appendTo(element), | |
context = canvas.get(0).getContext("2d"); | |
drawText(); | |
scramble(); | |
}); | |
drawText(); | |
function hover(x,y){ | |
var id = hoverArray.length; | |
hoverArray.push([x,y]); | |
setTimeout(function(){ | |
hoverArray[id] = undefined; | |
},1300); | |
} | |
$(document).click(function(){ | |
hoverArray = []; | |
}); | |
element.parent().on('mousemove touchmove',function(e){ | |
e.preventDefault(); | |
var x = e.pageX - element.offset().left; | |
var y = e.pageY - element.offset().top; | |
hover(x,y); | |
}); | |
var particle = function(x,y,visible,color){ | |
this.color = 'rgba('+color[0]+','+color[1]+','+color[2]+','+color[3] / 255+')'; | |
this.visible = visible; | |
this.realx = x; | |
this.realy = y; | |
this.toplace = false; | |
this.rate = Math.round(Math.random() * 12) - 8; | |
this.spin = Math.round(Math.random() * 2); | |
this.x = x; | |
this.y = y; | |
particles.push(this); | |
} | |
particle.prototype.draw = function(){ | |
var l = false; | |
for(var i = 0; i < hoverArray.length; i++){ | |
if(hoverArray[i]){ | |
if(this.realx >= hoverArray[i][0] - 25 && this.realx <= hoverArray[i][0] + 25 && hoverArray[i]){ | |
if(this.realy >= hoverArray[i][1] - 25 && this.realy <= hoverArray[i][1] + 25 && hoverArray[i]){ | |
this.toplace = true; | |
l = true; | |
} | |
} | |
} | |
} | |
if(l == false){ | |
this.toplace = false; | |
} | |
if(this.toplace == false){ | |
if(this.spin == 1){ | |
this.x = this.realx + Math.floor(Math.sin(currentTime) * this.rate); | |
} else if(this.spin == 0){ | |
this.y = this.realy + Math.floor(Math.cos(-currentTime) * this.rate); | |
} else { | |
this.x = this.realx + Math.floor(Math.sin(-currentTime) * this.rate); | |
this.y = this.realy + Math.floor(Math.cos(currentTime) * this.rate); | |
} | |
} else { | |
if(this.x < this.realx){ | |
this.x++; | |
} else if(this.x > this.realx){ | |
this.x--; | |
} | |
if(this.y < this.realy){ | |
this.y++; | |
} else if(this.y > this.realy){ | |
this.y--; | |
} | |
} | |
if(this.visible == true || this.toplace == true){ | |
context.fillStyle = this.color; | |
context.fillRect(this.x, this.y, 1,1); | |
} | |
} | |
function scramble(){ | |
for(var y = 1; y < ch; y+=1){ | |
for(var x = 0; x < cw; x++){ | |
if(context.getImageData(x, y, 1, 1).data[3] >= 1){ | |
if(Math.round(Math.random() * 3) >= 2){ | |
new particle(x,y,false,context.getImageData(x, y, 1, 1).data); | |
} else { | |
new particle(x,y,true,context.getImageData(x, y, 1, 1).data); | |
} | |
} | |
} | |
} | |
} | |
scramble(); | |
var requestframe = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame || | |
// IE Fallback, you can even fallback to onscroll | |
function (callback) { | |
window.setTimeout(callback, 1000 / 60); | |
}; | |
function loop(){ | |
var now = new Date().getTime(); | |
currentTime = (now - startTime) / 1000; | |
context.clearRect(0,0,cw,ch); | |
for(var i = 0; i < particles.length; i++){ | |
particles[i].draw(); | |
} | |
requestframe(loop); | |
} | |
loop(); | |
} | |
})(); |
<script src="https://code.jquery.com/jquery-2.2.4.min.js"></script> |
html, body { | |
background: rgb(34, 34, 34); | |
height: 100%; | |
.flex { | |
width: 100%; | |
color: rgb(255, 255, 255); | |
height: 100%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
transition: all 0.2s; | |
height: 100%; | |
} | |
.content { | |
width: 500px; | |
margin: auto auto; | |
h2 { | |
padding-top: 0%; | |
padding-left: 19px; | |
margin-bottom: 30px; | |
font-weight: bold; | |
font-family: didot; | |
font-size: 75px; | |
letter-spacing: .2px; | |
line-height: 75px; | |
max-width: 550px; | |
width: 100%; | |
} | |
.text { | |
padding: 0px 0 0 22px; | |
p { | |
font-family: didot; | |
font-size: 16px; | |
line-height: 32px; | |
max-width: 463px; | |
width: 100%; | |
letter-spacing: 1.1px; | |
padding: 0px !important; | |
margin-bottom: 20px; | |
} | |
} | |
} | |
} | |