将递归函数转换为异步CPS实现(javascript)

时间:2012-07-26 08:40:32

标签: javascript jquery dom asynchronous callback

这是我的功能。

    function duplicate_step_through_highlighted (element_jq, target_jq, char_cb) {
        console.log( element_jq);
        var contents = element_jq.contents();
        for (var i = 0 ; i < contents.length; ++i) {
            // if text node, step
            if (contents[i].nodeType === 3) {
                // insert empty text node
                var new_tn = document.createTextNode('');
                target_jq.append(new_tn);

                // iterate it 
                var text = contents[i].nodeValue;
                for (var j = 0; j < text.length; j++) {
                    char_cb(text[j],new_tn);
                    new_tn.nodeValue += text[j];
                    // *** I want an async delay here *** 
                }
            } else { // type should be 1: element
                // target_jq gets a duplicate element inserted, copying attrs
                var new_elem = $(contents[i].cloneNode(false)).appendTo(target_jq);
                duplicate_step_through_highlighted($(contents[i]),$(new_elem),char_cb);

                // then a recursive call is performed on the newly created element as target_jq
                // and the existing one as element_jq. char_cb is passed in
            }
        }
    }

我正在做的是通过一次重建一个字符来重建HTML元素。这样做是有充分理由的,我希望它的视觉效果能够“输入”。

所以现在没有延迟,所以我的元素立即重复。我已经检查过结果是否一致,但我很清楚,我可能需要完全重写函数,以便在插入每个字符后能够输入异步延迟。

我是否需要重新编写它并有一个堆栈来跟踪我在元素中的位置?

3 个答案:

答案 0 :(得分:4)

您可能需要查看我最近的answerthis older oneDemo),了解如何实施此类效果。


提示:不要将元素克隆到新的元素中,只需隐藏它们并使它们看起来是部分的。

此外,除了本机DOM元素之外,根本不应该处理jQuery实例。所以是的,重写可能会:-)而且我认为它确实需要一个堆栈。

function animate(elements, callback) {
/* get: array with hidden elements to be displayes, callback function */
    var i = 0;
    (function iterate() {
        if (i < elements.length) {
            elements[i].style.display = "block"; // show
            animateNode(elements[i], iterate); 
            i++;
        } else if (callback)
            callback();
    })();
    function animateNode(element, callback) {
        var pieces = [];
        if (element.nodeType==1) {
            while (element.hasChildNodes())
                pieces.push(element.removeChild(element.firstChild));
            setTimeout(function childStep() {
                if (pieces.length) {
                    animateNode(pieces[0], childStep); 
                    element.appendChild(pieces.shift());
                } else
                    callback();
            }, 1000/60);
        } else if (element.nodeType==3) {
            pieces = element.data.match(/.{0,2}/g); // 2: Number of chars per frame
            element.data = "";
            (function addText(){
                element.data += pieces.shift();
                setTimeout(pieces.length
                    ? addText
                    : callback,
                  1000/60);
            })();
        }
    }
}

animate($("#foo").children());

Demo at jsfiddle.net

工作原理:

  • addText函数为当前文本节点添加了一些字符,并为自身设置了超时 - 动画!万一完成,它会调用callback函数。
  • childStep在子节点上运行动画,并将自身作为回调传递,直到没有孩子离开 - 然后nvokes callback函数。
  • 两者一起,animateNode以递归方式在节点树上运行,并按顺序为文本节点设置动画。
  • iterate函数在所有输入元素上调用animateNode(解除它们之后),将自身作为回调传递。完成所有输入元素后,它会调用外部callback,它是animate的第二个参数。

答案 1 :(得分:2)

这是我的解决方案,它是一种更高效,更清洁,更快的方法:

var start = 0; //Makes sure you start from the very beggining of the paragraph.
var text = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Cras viverra sem dolor, nec tempor purus luctus vitae. Nulla massa metus, iaculis et orci euismod, faucibus fringilla metus. Sed pellentesque in libero nec.'; //Your text
var speed = 14; //Of course you can choose your own speed, 0 = instant, the more you add the slower it gets.
function typeWriter() {
  if (start < text.length) {
    document.querySelector('.demo').innerHTML += text.charAt(start);
    start++;
  }
  setTimeout(typeWriter, speed);
}
<body onload="typeWriter();">

<p class="demo"></p>

</body>

答案 2 :(得分:1)

我制作了一个在我的网站上使用的简单脚本,它可能会帮助那些希望实现这种效果的人。

以下是Github回购的link,以下是解释:

class Typer {

    constructor(typingSpeed, content, output) {

        this.typingSpeed = typingSpeed;
        // Parses a NodeList to a series of chained promises
        this.parseHtml(Array.from(content), output);
    };

    makePromise(node, output) {

        if (node.nodeType == 1) // element 
        {
            // When a new html tag is detected, append it to the document
            return new Promise((resolve) => {
                var tag = $(node.outerHTML.replace(node.innerHTML, ""));
                tag.appendTo(output);
                resolve(tag);
            });

        } else if (node.nodeType == 3) // text
        {
            // When text is detected, create a promise that appends a character
            // and sleeps for a while before adding the next one, and so on...
            return this.type(node, output, 0);
        } else {
            console.warn("Unknown node type");
        }
    }

    parseHtml(nodes, output) {
        return nodes.reduce((previous, current) => previous
            .then(() => this.makePromise(current, output)
                .then((output) => this.parseHtml(Array.from(current.childNodes), output))), Promise.resolve());
    }

    type(node, output, textPosition) {
        var textIncrement = textPosition + 1;

        var substring = node.data.substring(textPosition, textIncrement);

        if (substring !== "") {
            return new Promise(resolve => setTimeout(resolve, this.typingSpeed))
                .then(() => output.append(substring))
                .then(() => this.type(node, output, textIncrement));
        }

        return Promise.resolve(output);
    }
}