聖誕節,把你的 JavaScript 代碼都裝扮成聖誕樹吧
效果的話,可以去看一下我們公司的官網http://www.souche.com ),裡面涉及到的js代碼在今天大部分被臨時替換成了聖誕樹,開啟每個js代碼即可看到效果。
其實也不神奇,我們使用了自己寫的一個nodejs庫,如果您要實現這樣的效果,只需要按照下面第一章節的方法即可。當然您也可以在線壓縮代碼:http://f2e.souche.com/cheniu/js2image.html
下面分兩章節,分別講解如何使用js2image這個庫 和 js2image這個庫的原理。
js2image使用
github地址:https://github.com/xinyu198736/js2image 歡迎送上star或者follow。
js2image主要有兩個比較特殊的特性:
壓縮後的樣本可以查看這些js均來自搜車官網):
-
http://assets.souche.com/assets/js/souche.js souche主指令碼
-
http://assets.souche.com/assets/js/lib/jquery-1.7.1.min.js jquery 1.7.1
-
http://assets.souche.com/assets/js/lib/mustache.js mustache
使用方式很簡單:
- npm install js2image -g;
然後在存在js的檔案夾中執行:
- js2image -s ./resource/jquery.js
或者針對某個目錄下所有的js執行慎用),會深度遍曆此目錄裡所有的js檔案然後壓縮出.xmas.js尾碼的結果檔案。
- js2image -s ./resource/
即可產生一個對應的 **.xmas.js 的檔案。
如果要將js2image整合到gulp或者其他nodes項目中,可以使用用模組的形式:
- var Js2Image = require("js2image");//擷取結果的 codeJs2Image.getCode("./resource/jquery.js","./resource/tree.png", {}).then(function(code){
- console.log(code);
- })
更多的資訊可以參照github上的文檔。
如果只是要使用這個效果,看到這裡就ok啦,下面講解這個庫的原理,有些地方可能比較繞。
js2image實現原理
js2image的實現從宏觀來說,大體只有3個要點。
稍有想法的同學估計看到這裡基本已經明白是怎麼回事了,下面一一講解這3個要點。
① 從圖片產生2值得字元畫
這裡用到了一個現成的npm包:image-to-ascii 。這個庫的作用是用指定的字元來還原一個映像。而我們用這個庫來產生一個用 ☃字元和空格 分別表示黑和白的字元畫,然後將字元畫的每一行分解成數組的一個元素,供第二步使用,這就是我們中間產生的一個struct,代碼見 utils/image-to-struct.js
② 分割js源碼成盡量小的小塊。
這是非常重要的一步,js代碼具體可以分解成多細的小塊呢?
看下面一段代碼:
- !function
- (e,t
- ){ (
- "objec"
- +"t") ==
- typeof
- module && (
- "objec"+"t")
- == typeof module
- .exports?module.
- exports=e.document?t(e
- ,!0):function(e){if(!e.
- document) throw new Error (
- ("jQuer"+"y req"+"uires"+" a wi"
- +"ndow "+"with "+"a doc"+"ument") )
- ; return t (e)}:t(e)}( ("undef"+"ined")
- !=typeof window ?window:this,function(e,t){var
這是jQuery開始的一段代碼,可以看到,大部分操作符都允許中間插入任意多的空格或者換行,我們正是利用這一特性將js代碼解肢,然後拼接成任意形狀的圖片。
核心代碼其實就是一個正則,我們用這個正則把js源碼解構成一個數組,然後後續根據每行需要的字元數,從這個數組裡不斷取片段出來拼接。
- //分離代碼,以可分割單位拆分成數組。var lines = hold_code.replace(/([^a-zA-Z_0-9=!|&$])/g,"/n$1/n").split("/n");
- //有了這個lines數組之後後面就簡單了,根據第一步裡產生的struct不斷遍曆從lines抽取代碼填充到struct裡即可產生最終的代碼:
- while(lines.length>0){
- //迴圈往struct裡填充代碼
- struct.forEach(function(s){
- var chars_arr = s.replace(/ +/g," ");//一行有多組分離的*****
- var r = s;
- chars_arr.split(/ +/).forEach(function(chars){
- if(chars.length == 0){
- return;
- }
- var char_count = chars.length;
- //從lines裡取出char_count數量的代碼來填充,不一定精準,要確保斷行正確
- var l = pickFromLines(lines,char_count);
-
- r = r.replace(chars,function(){
- return l;
- })
- })
- result += r+"/n"
- })
-
- }
③ 保留不可分割的文法
注意:到了這一步,還很早,你分解出來的代碼是無法啟動並執行,很多不能換行和加空格的代碼都被你分開了,自然會報錯,那如何處理這些情況呢?
這一步,我們做的工作就是:
在執行代碼分拆之前,提取出代碼裡所有不可分割的文法,將他們保留在一個對象中,並且在原始碼中用預留位置替代這些文法,然後讓預留位置參與上個步驟的分離,因為預留位置是一個完整的連字號變數,所以不會被分割。在分割完成之後,我們再把這些預留位置替換回來即可。
不過,在js中哪些文法必須是串連在一起才能正常啟動並執行呢?
這裡總結下:
- var double_operator = ["==", ">=", "<=", "+=", "-=", "*=", "/=", "%=", "++", "--", "&&", "||", ">>", "<<"]
- var three_operator = ['===', '!==']
一些固定文法,可以用正則表達,如下:
- var reg_operator = [
- {
- start:"return",
- reg:/^return[^a-zA-Z_0-1"'][a-zA-Z_0-1.]+/
- // return 0.1 或者 return function 或者return aaabb
- },
- {
- start:"return/"",
- reg:/^return".*?"/ // return "d" 或者 return ""
- },
- {
- start:"return/'",
- reg:/^return'.*?'/ // return 'd' 或者 return ''
- },
- {
- start:"throw",
- reg:/^throw [a-zA-Z_0-1]+?/ //throw new 或者 throw obj
- }
- ]
小數點文法,例如 0.01 因為之前我們用點號來分割代碼的,但是這裡的點號不能作為分割符使用,需要保留前後數字跟點號在一行 其他文法,例如 value++ 之類的文法,變數和操作符之間不可分割。 那我們如何從原始碼中解析出這些文法,然後做處理呢?
核心代碼均在 utils/keep-line.js 中
核心演算法,事實上是通過一個對字串的遍曆來完成的,然後在遍曆每個字元的時候都會判斷是否進入某個邏輯來跳躍處理。
例如,判斷出當前在雙引號內,則進入字串提取邏輯,一直到字串結束的時候再繼續正常的遍曆。
其他動作符和Regex的演算法也是類似,不過裡面很多細節需要處理,例如逸出字元之類的。
有些比較特殊的,例如小數點文法的提取,在判斷到當前字元是點號之後,需要往前和向後迴圈尋找數字,然後把整個文法找出來。
這裡不細講,在keep-line.js 這個檔案中又一大坨代碼做這個事情的。
④ 字串解構
做到這一步的時候,其實效果已經很不錯了,也可以保證代碼的可運行,但是代碼裡有些字串很長,他們總是會被被保留在一行裡,這樣就造成他會影響一些圖案的邊緣的準確性代碼分離原則是越細越好,就是為這個考慮)。
我們如何處理呢,那就是將字串解構,以5個為單位將字串分離成小塊。
這裡有兩個比較重要的問題需要處理;
結語
至此,整個應用就完成了,可以順利完成從任意js和映像產生圖形代碼了。
再說一遍項目開源地址:https://github.com/xinyu198736/js2image 歡迎star,順便follow下樓主就更開心了。