最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - How to scroll to bottom of div when new elements are added - Stack Overflow

programmeradmin0浏览0评论

I am trying to make a chat application using Vue and Express.

At the moment, I want to make the container with the messages automatically scroll to the bottom when a new message is sent. I tried to do this by using a scrollToEnd function that selects the div container and assigns its scrollHeight to the scrollTop:

scrollToEnd: function () {
    var messages = this.$el.querySelector('#messages')
    messages.scrollTop = messages.scrollHeight
}

This gives the following error:

TypeError: Cannot read property 'scrollHeight' of null

For some reason, using the querySelector always returns null, also when I am testing it on other elements.

Below the full code for the ponent can be found.

<template>
    <div id="messages">
        <ul>
            <li v-for="msg in messages.slice().reverse()">{{ msg.message }}</li>
        </ul>
    </div>
</template>

<script>
import MessageService from '@/services/MessageService'

export default {
    name: 'messages',
    data () {
        return {
            messages: []
        }
    },
    mounted () {
        this.getMessages()

        this.$root.$on('newMessage', (msg) => {
            this.message = msg
            this.getMessages()
            this.scrollToEnd()
        })
    },
    methods: {
        async getMessages () {
            const response = await MessageService.fetchMessages()
            this.messages = response.data.messages
        },
        scrollToEnd: function () {
            var messages = this.$el.querySelector('#messages')
            messages.scrollTop = messages.scrollHeight
        }
    }
}
</script>

I am trying to make a chat application using Vue and Express.

At the moment, I want to make the container with the messages automatically scroll to the bottom when a new message is sent. I tried to do this by using a scrollToEnd function that selects the div container and assigns its scrollHeight to the scrollTop:

scrollToEnd: function () {
    var messages = this.$el.querySelector('#messages')
    messages.scrollTop = messages.scrollHeight
}

This gives the following error:

TypeError: Cannot read property 'scrollHeight' of null

For some reason, using the querySelector always returns null, also when I am testing it on other elements.

Below the full code for the ponent can be found.

<template>
    <div id="messages">
        <ul>
            <li v-for="msg in messages.slice().reverse()">{{ msg.message }}</li>
        </ul>
    </div>
</template>

<script>
import MessageService from '@/services/MessageService'

export default {
    name: 'messages',
    data () {
        return {
            messages: []
        }
    },
    mounted () {
        this.getMessages()

        this.$root.$on('newMessage', (msg) => {
            this.message = msg
            this.getMessages()
            this.scrollToEnd()
        })
    },
    methods: {
        async getMessages () {
            const response = await MessageService.fetchMessages()
            this.messages = response.data.messages
        },
        scrollToEnd: function () {
            var messages = this.$el.querySelector('#messages')
            messages.scrollTop = messages.scrollHeight
        }
    }
}
</script>
Share Improve this question edited Apr 5, 2018 at 23:38 Emile Bergeron 17.4k5 gold badges85 silver badges131 bronze badges asked Apr 5, 2018 at 21:16 joedoesnotknowjoedoesnotknow 731 gold badge2 silver badges8 bronze badges
Add a ment  | 

1 Answer 1

Reset to default 6

this.$el

The root DOM element that the Vue instance is managing.

this.$el is the #messages div, there's no need to fetch it from the DOM.

Then, you could use this.$el.lastElementChild.offsetTop to get the last message and scroll to its top, so if it's long, you're not scrolling past its starting point.

Here, I simplified the template a little to make it straight to the point.

<template>
    <ul id="messages">
        <li v-for="msg in messages.slice().reverse()">{{ msg.message }}</li>
    </ul>
</template>

<script>
export default {
    name: 'messages',
    data() {
        return { messages: [] };
    },
    mounted() {
        this.getMessages();
    },
    updated() {
        // whenever data changes and the ponent re-renders, this is called.
        this.$nextTick(() => this.scrollToEnd());
    },
    methods: {
        async getMessages () {
            // ...snip...
        },
        scrollToEnd: function () {
            // scroll to the start of the last message
            this.$el.scrollTop = this.$el.lastElementChild.offsetTop;
        }
    }
}
</script>

If you really want to keep the <div> container, you could use a ref.

<template>
    <div id="messages">
        <ul ref="list">
            <li v-for="msg in messages.slice().reverse()">{{ msg.message }}</li>
        </ul>
    </div>
</template>

Then in the ponent, you can refer to it with this.$refs.list.

ref is used to register a reference to an element or a child ponent. The reference will be registered under the parent ponent’s $refs object. If used on a plain DOM element, the reference will be that element; if used on a child ponent, the reference will be ponent instance.

While Vue examples often use the native DOM API to get around, using ref in this instance is way easier.

发布评论

评论列表(0)

  1. 暂无评论