angularjs特效之分散的字符串--解析compile link $compile $interpolate

效果預覽

來自codepen的效果預覽:(引用了angularjs,已被牆,請謹慎預覽)
http://codepen.io/flybywind/pen/aNjxJacss

轉載聲明

這篇文章其實源自BenNadel的博客。在此我要分享一下本身的收穫和一些更改。html

主要內容

整個效果都創建在angularjs的directive裏面。能夠想到,做者綁定了mousemove事件,經過勾股定理計算的距離,除此以外,我還有些小收穫要跟你們分享一下:node

  • directive中compile和link函數的區別jquery

  • $interpolate和$compile這2個service的區別angularjs

  • 如何把node內部包含的全部文本字母都用span包裹起來chrome

  • relative定位和absolute定位的差異express

directive中compile和link函數的區別

根據BenNadel本身在博客中的解釋:瀏覽器

I use the compile() phase when I need to alter the structure of the DOM (Document Object Model) in a way that is based on nothing but the existing structure of DOM.app

翻譯成漢字就是:當我須要根據原始DOM(文檔對象模型,說詳細了可能看不懂,仍是用簡稱吧~)的結構對Dom進行改變時,我使用compile函數。
道理其實也很簡單,由於angluar會對Dom結構進行改變,當你進入link函數後,你看到的Dom結構可能跟你預期的不同,找不到你要的元素了,此時不少人可能就懵圈了。在BenNadel的另外一篇博客中,他就給出了一個例子:首先他在一個directive中使用了template,在transclude部分(嵌入部分)中使用了ngRepeat。他發現,只有在Compile階段,才能訪問到使用了ngRepeat的元素,而且改變它們的class;而在link階段,是找不到的。
緣由就是ngRepeat這個directive已經把那些元素從Dom樹中刪除了,它們會在隨後被加入,可是在link階段你是找不到的
因此,在這種可能不少人一生都見不到的狀況下,你才必須使用compile。
在以上的例子中,BenNadel還利用priority對同一個directive進行了兩階段處理,這種奇技淫巧曆來沒見過,領教了。dom

其實不少時候,在link和compile中處理的結果是同樣的,可是細節上有些差別,好比如下狀況:

<span
    ng-repeat="word in [ 'Word', 'to', 'your', 'mother' ]"
    bn-compile
    bn-link>
    {{ word }}
</span>

其中bn-compile和bn-link分別是2個directive,一個在compile函數中打印出日誌「compile」,而且給element增長class "c1",一個在link函數中打印出「link」,而且給element增長class "c2",c一、c2都有css樣式進行展現。最終結果是生成的4個span均可以看到c一、c2的樣式,可是console中compile只打印了1次,link卻打印了4次。
這是由於,bn-compile是在compile階段修改的dom,它修改的是原始Dom結構,這跟你在html文檔中,手動寫上 class="c1"是同樣的。

固然,在link函數中,也有一些地方是compile作不到的,有的時候你可能就是要對已經部分渲染後的Dom結構進行修改,並且link函數中能夠傳入scope,此時你能夠進行數據綁定了。這也就是下面我要講到的。

$interpolate和$compile這2個service的區別

在BenNadel的原始文章中,他使用了compile對dom結構進行修改。可是若是個人文本內容是經過數據綁定了,以下:

<div 
    scatter-effect="#c2"
    scatter-distance="1000"
    class="tagline">
    {{hello}}
</div>

特效就失敗了,以下:
圖片描述

第二個div由於綁定的是動態數據,最終是以模板形式顯示的。
爲了彌補這個缺陷,我決定改爲在link函數中對Dom進行操做。其實就是把BenNadel在compile中的代碼放到link最開始執行就好了。另外,還須要對{{}}進行手動編譯,此時$interpolate就要閃亮登場了。
如下是個人部分js代碼:

angular.module("Demo").directive(
        "scatterEffect",
// $compile是把字符串編譯爲element,因此字符串必須是dom形式
// $interpolate是把表達式和data 綁定到一塊兒,$compile中會調用$interpolate
    function ($document, $interpolate) {
        return ({
            link: link,
            restrict: "A"
        });

    function link(scope, element, attributes) {
       // 若是是compile,則沒法獲取scope
       var realContent = $interpolate(element.html())(scope);
       element.empty().append(realContent);
       // 這是原來compile中的函數,element處理後,再放到link中調用
       wrapLetters(element);
           /*  other code  */
    }
 })

在以上代碼中,element.html()返回的是字符串,好比在處理第一個div時,就是Happy Friday You <em>Beautiful</em> People!,第二個div就是{{hello}},開始我嘗試用$compile編譯這個字符串,結果控制檯顯示 unrecognized expression: Happy Friday You <em>Beautiful</em> People!以及unrecognized expression: {{hello}} !問了一下google大叔,發現原來要用$interpolate才行。
$compile是把字符串編譯爲element,因此字符串必須是dom形式;$interpolate是把表達式和data綁定到一塊兒,$compile中會調用$interpolate,後者只是前者的子過程。顯然,在我這個狀況下,用$interpolate才行。
其實Happy Friday You <em>Beautiful</em> People!扔到chrome,會自動給你加html和body使其正常顯示,可是angular無法替你加,加上就亂套了。因此$compile必須接受標準的html語句,此處能夠用正則判斷一下。我就懶得寫了。

如何把node內部包含的全部文本字母都用span包裹起來

這句話儘管很長,可是作起來,其實也很難(⊙﹏⊙)b。在此我只能奉上大神的代碼和個人膝蓋了,謹和諸君分享:

function findTextNodes(parent) {
                // node是一個DOMElement, map返回一個jQuery對象,因此要想得到array可使用toArray() or get()
                /* 個人註釋
                    If the node is an element node, the nodeType property will return 1.
                    If the node is an attribute node, the nodeType property will return 2.
                    If the node is a text node, the nodeType property will return 3.
                    If the node is a comment node, the nodeType property will return 8.
                */
                // 此處的angular.element就是jQuery
                var textNodes = angular.element(parent).contents().map(
                    function operator(i, node) {
                        return ((node.nodeType === 1) ? findTextNodes(node) : node);

                    }
                );

                return (textNodes.toArray());

            }

            // I find and wrap each text-based letter, within the given parent, 
            // in its own Span tag.
            function wrapLetters(parent) {

                findTextNodes(parent).forEach(
                    function(node) {
                        // Replace each individual letter with a Span tag.         
                        var wrappedHtml = node.nodeValue.replace(/(\S)/g, "<span class='scatter-item'>$1</span>");
                        // 最值得推敲的在這裏:
                        var fragment = angular.element(document.createDocumentFragment())
                            .append(wrappedHtml);

                        node.parentNode.insertBefore(fragment[0], node);
                        node.parentNode.removeChild(node);
                        // 如下方法不行:jquery生成的元素列表默認丟棄了首尾空格!
                        //$(wrappedHtml).insertBefore(node);
                        //node.parentNode.removeChild( node );
                    }
                );

            }

其實提取textNode,而後正則替換,最後插入原來的位置,聽起來不難,可是若是利用jQuery把字符串轉換爲dom,那麼兩頭的空格就會丟掉,好比Happy Friday You 這個node,正則替換後wrappedHtml是這樣的:
圖片描述

jQuery轉換後,先後的空格都沒了。因此不能用它來轉換。
(哪位大神若是知道有能夠控制jquery不丟空格的方法,請不吝賜教!)
最後,BenNadel大神也只好使用原生js進行dom樹的替換了。

relative定位和absolute定位的差異

下面咱們說點關於css的知識。我常常對absolute元素進行top、left、right、bottom定位,我也知道這樣作以前,父元素必須用relative,可是我還真不知道原來relative元素也能夠用TLRB這四兄弟進行操做!真是丟人,確實須要反思啊。下面附上英文解釋和中文對照翻譯:

static Default value. Elements render in order, as they appear in the document flow
absolute The element is positioned relative to its first positioned (not static) ancestor element
fixed The element is positioned relative to the browser window
relative The element is positioned relative to its normal position, so "left:20px" adds 20 pixels to the element's LEFT position
initial Sets this property to its default value.

static是position的默認值,什麼都不加就是那種效果;
absolute,你們最熟悉,TLRB指定的是和最近的relative父元素之間的相對位置
fixed,和瀏覽器窗口的相對位置,也比較簡單。
重點來了,relative是和初始位置的相對位置。這也正是relative這個名字的含義,之前都沒仔細想過。。。
initial就是元素的初始值。有些元素的初始值可能不是static。反正各類奇葩的元素均可能,或者未來可能出現。
因此,總結一下,absolute、fixed和relative都是position類型,只是相對的位置不一樣而已。
BenNadel正是利用了relative的這個特性,給每一個span元素都設置爲:

span.scatter-item {
    position: relative ;
    z-index: 100 ;
}

而後,在mousemove事件中,調整top、left的值,使它們在鼠標距離目標元素遠的時候,偏離本身的位置;在鼠標進入目標元素時,從新回到本身的位置。
(z-index好像沒用~~)

結語

終於碼完了

相關文章
相關標籤/搜索