Vue 3: How to use Watch/WatchEffect to trigger message when a Typed.js phrase completes?

1.1k Views Asked by At

I have some Vue 2 code that I'm trying to upgrade to Vue 3, but I'm having a lot of trouble with an instance of the Typed.js library. Here is a minimal working example of what I have in Vue 2:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <title>CDN with Vue 2</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>
    <div id="app">

        <h1>Here's the typed phrase:</h1>
        <h2 id="typing"></h2>
        <div v-if="showMessage">
            And now the typing is complete and this message appears!
        </div>
    </div>


    <script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.0/typed.min.js"></script>
    <script>
        var app = new Vue({
            el: '#app',
            data() {
                return {
                    typed: "",
                    showMessage: false,
                };
            },
            mounted() {
                this.initTyped();
            },
            methods: {
                initTyped() {
                    this.typed = new Typed("#typing", {
                        strings: ["Hi, I'm Bill. <br/> I'm a developer."],
                        loop: false,
                        showCursor: false,
                        typeSpeed: 35,
                    });
                }
            },
            watch: {
                "typed.typingComplete": function () {
                    if (this.typed.typingComplete) {
                        this.showMessage = true;
                    }
                },
            },
        })
    </script>
</body>
</html>

The above code should work as is, just paste it into a file and take a look. It should type out a sentence, and when it's done typing, a message will appear.

I'm new to Vue 3, so apologies for all the mistakes the following is likely to have, but this is the gist of what I would like to get to work:

<html>

<head>
    <meta charset="UTF-8">
    <title>CDN with Vue 2</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <h1>Here's the typed phrase:</h1>
        <h2 id="typing"></h2>
        <div v-if="showMessage">
            And now the typing is complete and this message appears!
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.0/typed.min.js"></script>

    <script>
        const { ref, createApp, onMounted, watchEffect } = Vue
        createApp({
            setup() {

                let showMessage = ref(false)

                const initTyped = () => {
                    const typed = new Typed("#typing", {
                        strings: ["Hi, I'm Bill. <br/> I'm a developer."],
                        loop: false,
                        showCursor: false,
                        typeSpeed: 35,
                    });
                }

                onMounted(() => {
                    initTyped()
                })

                watchEffect(() => {
                    if (typed.typingComplete) {
                        showMessage = true;
                    }
                });

                return {
                    showMessage
                }
            }
        }).mount('#app')
    </script>
</body>
</html>

I have no idea whether I should be using Watch/WatchEffect, I'm guessing I'm declaring and updating the showMessage variable all wrong, I have no idea how to set the typed variable so it's available to watchEffect, and I generally don't know what I'm doing with the composition API!

Also before you recommend vue-typed-js, it appears that this library is not compatible with Vue 3 yet; but I didn't use it in Vue 2 anyway.

Any help is appreciated, and some code in Vue 3 would be amazing!

1

There are 1 best solutions below

0
tony19 On BEST ANSWER

The equivalent Vue 3 code would be:

  1. Declare typed as a ref.

  2. In initTyped(), set typed's value to the new Typed instance (via its .value property).

  3. In the watcher, set showMessage's value to true (via its .value property).

1️⃣
let typed = ref()

const initTyped = () => {
    2️⃣
    typed.value = new Typed("#typing", {⋯});
}


onMounted(() => {
    initTyped()
})

watchEffect(() => {
    if (typed.typingComplete) {
        3️⃣
        showMessage.value = true;
    }
});

<html>

<head>
    <meta charset="UTF-8">
    <title>CDN with Vue 2</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <h1>Here's the typed phrase:</h1>
        <h2 id="typing"></h2>
        <div v-if="showMessage">
            And now the typing is complete and this message appears!
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.0/typed.min.js"></script>

    <script>
        const { ref, createApp, onMounted, watchEffect } = Vue
        createApp({
            setup() {

                let showMessage = ref(false)
                let typed = ref()
                
                const initTyped = () => {
                    typed.value = new Typed("#typing", {
                        strings: ["Hi, I'm Bill. <br/> I'm a developer."],
                        loop: false,
                        showCursor: false,
                        typeSpeed: 35,
                        onComplete: () => showMessage.value = true
                    });
                }

                onMounted(() => {
                    initTyped()
                })

                watchEffect(() => {
                    if (typed.typingComplete) {
                        showMessage.value = true
                    }
                })
                
                return {
                    showMessage
                }
            }
        }).mount('#app')
    </script>
</body>
</html>

But you don't need to store a reference to the Typed instance. The Typed options includes an onComplete callback property that could be used instead of a watcher, so your code could be simplified to this:

const initTyped = () => {
    new Typed("#typing", {
        ⋮
        onComplete: () => showMessage.value = true, 
    });
}

onMounted(() => {
    initTyped()
})

<html>

<head>
    <meta charset="UTF-8">
    <title>CDN with Vue 2</title>
    <script src="https://unpkg.com/vue@next"></script>
</head>

<body>
    <div id="app">
        <h1>Here's the typed phrase:</h1>
        <h2 id="typing"></h2>
        <div v-if="showMessage">
            And now the typing is complete and this message appears!
        </div>
    </div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/typed.js/2.0.0/typed.min.js"></script>

    <script>
        const { ref, createApp, onMounted } = Vue
        createApp({
            setup() {

                let showMessage = ref(false)

                const initTyped = () => {
                    new Typed("#typing", {
                        strings: ["Hi, I'm Bill. <br/> I'm a developer."],
                        loop: false,
                        showCursor: false,
                        typeSpeed: 35,
                        onComplete: () => showMessage.value = true
                    });
                }

                onMounted(() => {
                    initTyped()
                })

                return {
                    showMessage
                }
            }
        }).mount('#app')
    </script>
</body>
</html>