Skip to content

Instantly share code, notes, and snippets.

@arlimus
Last active December 10, 2015 23:28
Show Gist options
  • Save arlimus/4509918 to your computer and use it in GitHub Desktop.
Save arlimus/4509918 to your computer and use it in GitHub Desktop.
Small coffeescript to make text animate in a way similar to the one seen on 29c3.
# usage:
# eg have this in your html:
# <pre class="29c3">
# N.O-T/M
# Y-D/E.P
# A/R.T-M
# E-N/T
# 2.9-C/3
# </pre>
# then run this in javascript:
# ccc29c3ify($(".29c3"));
# take some string and scramble it in 29c3-style
# strength is a factor to control this from 0.0 = no scramble to 1.0 max strength
# lower strength will have less letter-swapping
scramble = (txt, strength) ->
strength = 0 if strength < 0
# variables we need:
spacers = ".,:;-/\\()[]{}~+'\""
text_re = /[a-z0-9 ]/i
# manipulators for strength
# use a function plotting tool to look at the curves ...
spacer_strength = strength * 3.0 + 0.2
line_flipper_strength = 0 - Math.log(1-Math.pow(strength,3))/Math.log(1.3)
block_flipper_strength = 0.3
# depending on spacer_strength, change char into
# one of the available spacers
scramble_spacer = (char) ->
# break method: don't change the char if we are
return char if( Math.random() > spacer_strength )
spacers[ Math.floor( Math.random() * spacers.length ) ]
# take a line (string) and for all spacers (i.e. not part of the text)
# attemt to scramble it
scramble_line_spacer = (line) ->
(for c in line
if not text_re.exec(c)
scramble_spacer c
else c
).join("")
# take a string org and switch characters at idx_old with idx_new
flip = (org, idx_old, idx_new) ->
o = org.split ""
tmp = o[idx_new]
o[idx_new] = o[idx_old]
o[idx_old] = tmp
o.join ""
# for a position org element in min..max, try to move it a certain distance forwards or backwards
# e.g. position = 3
# distance = 1
# direction = true => move to 4
# direction = false => move to 2
# if a boundary is crossed, try to move into the opposite direction instead
flip_position = (org, min, max, distance, direction) ->
mod = if(direction) then distance else 0-distance
nu = org + mod
if(nu>max) then nu = org - mod
if(nu<min) then nu = org + mod
if(nu>max or nu <min) then nu = org
return nu
# go through the line and for each letter at chance lfp flip its position with an adjacent letter
# once flipped the chance lfp decreases
scramble_line_flipper = (line) ->
lfp = line_flipper_strength
for c in [1..line.length]
if Math.random() < lfp and text_re.exec(c)
lfp = lfp/2 # reduce the chance of this happening again
cn = flip_position( c-1, 0, line.length-1, 1, (Math.random() > 0.5) )
line = flip(line, c-1, cn )
line
# for a line try to scramble spacers and then letters
scramble_line = (line) ->
scramble_line_flipper scramble_line_spacer line
# split input string into lines and try to scramble lines separately
(scramble_line l for l in txt.split("\n")).join("\n")
scramble_timer = ( el, txt, cur_time, total_time ) ->
# failsafe, anything below this speed cannot be handled by browsers correctly
min_speed = 10
# failsafe, anything above for total time will screw the animation unintentionally
max_total_time = 20000
max_timeout = 5000
f = (str = strength) ->
el.textContent = scramble(txt, str )
rel_total_time = if(total_time > max_total_time) then max_total_time else total_time
x = cur_time/rel_total_time
# failsafe
x = 1.0 if x > 1.0
# strength = 1-x
strength = 1 - (Math.atan(4*(x+0.01))/(Math.PI/2)) - 0.15
irratic_curve = 1 - (Math.atan(2*(x+0.01))/(Math.PI/2)) + 0.5
# only proceed if we haven't hit the maximum animation time
if cur_time < total_time
# cur_timeout = Math.floor( 10 * ( Math.exp(x*5) - Math.pow(4.5, 5*(x-0.4)) )) + min_speed
cur_timeout = Math.floor( max_timeout * Math.atan(0.5*x)) + min_speed
# failsafe
cur_timeout = min_speed if cur_timeout < min_speed
$("#debug_29c3").html([
"timeout: #{cur_timeout}",
"strength: #{Math.round(strength*100)/100}",
"time: #{cur_time}/#{total_time}"
].join(" -- "))
# add some irratic changes
if( cur_timeout > 200 and Math.random() > irratic_curve )
# irratic changes are run via scrambling at this point in time with higher strenght
# and adding a short timeout for another scrambler at normal strength
ir_timeout = Math.random()*70 + 100 + min_speed
f(strength+0.25)
setTimeout( f, ir_timeout )
else
f()
setTimeout( ->
scramble_timer(el, txt, cur_time + cur_timeout, total_time)
, cur_timeout)
else
# at the end, reset the elements content to their clear/unscrambled state
el.textContent = txt
window.ccc29c3ify = (els, duration = 60000) ->
for el in els
scramble_timer( el, el.textContent, 0, duration )
#el.textContent = scramble(txt, .5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment