[{"data":1,"prerenderedAt":645},["ShallowReactive",2],{"\u002Farticles\u002Fscroll-to-bottom-vue-with-hidden-elements":3},{"id":4,"title":5,"body":6,"createdAt":634,"description":635,"extension":636,"meta":637,"navigation":105,"path":641,"seo":642,"stem":643,"__hash__":644},"articles\u002Farticles\u002F1.scroll-to-bottom-vue-with-hidden-elements.md","How to automatically scroll to bottom of an element after toggling its visibility in Vue 3.",{"type":7,"value":8,"toc":632},"minimark",[9,13,23,26,29,32,346,349,352,358,365,368,628],[10,11,5],"h1",{"id":12},"how-to-automatically-scroll-to-bottom-of-an-element-after-toggling-its-visibility-in-vue-3",[14,15,16],"p",{},[17,18,19],"strong",{},[20,21,22],"em",{},"Nov 22, 2024 10:26AM",[14,24,25],{},"When you need to automatically scroll an element to its bottom after toggling it into view in Vue 3, you would notice things doesn't actually work properly if you call the scroll function immediately after toggling the element's visibility.",[14,27,28],{},"Let's consider this scenario where you have a scrollable element that is hidden by default and a user has to click on a checkbox to make it visible.\nIf there is vital information at the bottom of the element, in most cases you want to automatically scroll to the botton as soon as the element is visible.",[14,30,31],{},"Below is a code snippet of how you might implement this.",[33,34,39],"pre",{"className":35,"code":36,"language":37,"meta":38,"style":38},"language-vue shiki shiki-themes dracula","\u003Cscript setup lang=\"ts\">\nimport { ref } from \"vue\";\n\nconst scrollEl = ref\u003CHTMLDivElement | null>(null);\nconst isVisible = ref(false);\n\nconst toggleVisibility = () => {\n  isVisible.value = !isVisible.value;\n\n  if (isVisible.value && scrollEl.value) {\n    scrollEl.value.scrollTop = scrollEl.value.scrollHeight;\n  }\n};\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton type=\"button\" @click=\"toggleVisibility\">Toggle Me\u003C\u002Fbutton>\n  \u003Cdiv ref=\"scrollEl\">Element with long scrollable content...\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue","",[40,41,42,78,100,107,145,165,170,190,204,209,224,235,241,247,257,262,272,311,337],"code",{"__ignoreMap":38},[43,44,47,51,55,59,62,65,69,73,75],"span",{"class":45,"line":46},"line",1,[43,48,50],{"class":49},"sCdxs","\u003C",[43,52,54],{"class":53},"s0Tla","script",[43,56,58],{"class":57},"sY_PY"," setup",[43,60,61],{"class":57}," lang",[43,63,64],{"class":53},"=",[43,66,68],{"class":67},"seVfx","\"",[43,70,72],{"class":71},"s-mGx","ts",[43,74,68],{"class":67},[43,76,77],{"class":49},">\n",[43,79,81,84,87,90,93,95,97],{"class":45,"line":80},2,[43,82,83],{"class":53},"import",[43,85,86],{"class":49}," { ref } ",[43,88,89],{"class":53},"from",[43,91,92],{"class":67}," \"",[43,94,37],{"class":71},[43,96,68],{"class":67},[43,98,99],{"class":49},";\n",[43,101,103],{"class":45,"line":102},3,[43,104,106],{"emptyLinePlaceholder":105},true,"\n",[43,108,110,113,116,118,122,124,128,131,135,138,142],{"class":45,"line":109},4,[43,111,112],{"class":53},"const",[43,114,115],{"class":49}," scrollEl ",[43,117,64],{"class":53},[43,119,121],{"class":120},"sAOxA"," ref",[43,123,50],{"class":49},[43,125,127],{"class":126},"sGEwX","HTMLDivElement",[43,129,130],{"class":53}," |",[43,132,134],{"class":133},"sCp4m"," null",[43,136,137],{"class":49},">(",[43,139,141],{"class":140},"sIQBb","null",[43,143,144],{"class":49},");\n",[43,146,148,150,153,155,157,160,163],{"class":45,"line":147},5,[43,149,112],{"class":53},[43,151,152],{"class":49}," isVisible ",[43,154,64],{"class":53},[43,156,121],{"class":120},[43,158,159],{"class":49},"(",[43,161,162],{"class":140},"false",[43,164,144],{"class":49},[43,166,168],{"class":45,"line":167},6,[43,169,106],{"emptyLinePlaceholder":105},[43,171,173,175,178,181,184,187],{"class":45,"line":172},7,[43,174,112],{"class":53},[43,176,177],{"class":120}," toggleVisibility",[43,179,180],{"class":53}," =",[43,182,183],{"class":49}," () ",[43,185,186],{"class":53},"=>",[43,188,189],{"class":49}," {\n",[43,191,193,196,198,201],{"class":45,"line":192},8,[43,194,195],{"class":49},"  isVisible.value ",[43,197,64],{"class":53},[43,199,200],{"class":53}," !",[43,202,203],{"class":49},"isVisible.value;\n",[43,205,207],{"class":45,"line":206},9,[43,208,106],{"emptyLinePlaceholder":105},[43,210,212,215,218,221],{"class":45,"line":211},10,[43,213,214],{"class":53},"  if",[43,216,217],{"class":49}," (isVisible.value ",[43,219,220],{"class":53},"&&",[43,222,223],{"class":49}," scrollEl.value) {\n",[43,225,227,230,232],{"class":45,"line":226},11,[43,228,229],{"class":49},"    scrollEl.value.scrollTop ",[43,231,64],{"class":53},[43,233,234],{"class":49}," scrollEl.value.scrollHeight;\n",[43,236,238],{"class":45,"line":237},12,[43,239,240],{"class":49},"  }\n",[43,242,244],{"class":45,"line":243},13,[43,245,246],{"class":49},"};\n",[43,248,250,253,255],{"class":45,"line":249},14,[43,251,252],{"class":49},"\u003C\u002F",[43,254,54],{"class":53},[43,256,77],{"class":49},[43,258,260],{"class":45,"line":259},15,[43,261,106],{"emptyLinePlaceholder":105},[43,263,265,267,270],{"class":45,"line":264},16,[43,266,50],{"class":49},[43,268,269],{"class":53},"template",[43,271,77],{"class":49},[43,273,275,278,281,284,286,288,290,292,295,297,299,302,304,307,309],{"class":45,"line":274},17,[43,276,277],{"class":49},"  \u003C",[43,279,280],{"class":53},"button",[43,282,283],{"class":57}," type",[43,285,64],{"class":53},[43,287,68],{"class":67},[43,289,280],{"class":71},[43,291,68],{"class":67},[43,293,294],{"class":57}," @click",[43,296,64],{"class":53},[43,298,68],{"class":67},[43,300,301],{"class":71},"toggleVisibility",[43,303,68],{"class":67},[43,305,306],{"class":49},">Toggle Me\u003C\u002F",[43,308,280],{"class":53},[43,310,77],{"class":49},[43,312,314,316,319,321,323,325,328,330,333,335],{"class":45,"line":313},18,[43,315,277],{"class":49},[43,317,318],{"class":53},"div",[43,320,121],{"class":57},[43,322,64],{"class":53},[43,324,68],{"class":67},[43,326,327],{"class":71},"scrollEl",[43,329,68],{"class":67},[43,331,332],{"class":49},">Element with long scrollable content...\u003C\u002F",[43,334,318],{"class":53},[43,336,77],{"class":49},[43,338,340,342,344],{"class":45,"line":339},19,[43,341,252],{"class":49},[43,343,269],{"class":53},[43,345,77],{"class":49},[14,347,348],{},"You would notice the above code doesn't produce the desired output.",[14,350,351],{},"As mentioned in the Vue documentation:",[353,354,355],"blockquote",{},[14,356,357],{},"When you mutate reactive state in Vue, the resulting DOM updates are not applied synchronously. Instead Vue buffers them until the \"next tick\" to ensure that each component updates only once no matter how many state changes you have made.",[14,359,360,361,364],{},"So a simple fix for the above issue is to call the ",[40,362,363],{},"nextTick"," utility function before the scrolling part of the code.",[14,366,367],{},"Here is an updated version of the code:",[33,369,371],{"className":35,"code":370,"language":37,"meta":38,"style":38},"\u003Cscript setup lang=\"ts\">\nimport { ref, nextTick } from \"vue\"; \u002F\u002F Updated import\n\nconst scrollEl = ref\u003CHTMLDivElement | null>(null);\nconst isVisible = ref(false);\n\n\u002F\u002F Function now marked as async\nconst toggleVisibility = async () => {\n  isVisible.value = !isVisible.value;\n\n  await nextTick(); \u002F\u002F Notice the new change here\n\n  if (isVisible.value && scrollEl.value) {\n    scrollEl.value.scrollTop = scrollEl.value.scrollHeight;\n  }\n};\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cbutton type=\"button\" @click=\"toggleVisibility\">Toggle Me\u003C\u002Fbutton>\n  \u003Cdiv ref=\"scrollEl\">Element with long scrollable content...\u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n",[40,372,373,393,415,419,443,459,463,468,485,495,499,513,517,527,535,539,543,551,555,563,596,619],{"__ignoreMap":38},[43,374,375,377,379,381,383,385,387,389,391],{"class":45,"line":46},[43,376,50],{"class":49},[43,378,54],{"class":53},[43,380,58],{"class":57},[43,382,61],{"class":57},[43,384,64],{"class":53},[43,386,68],{"class":67},[43,388,72],{"class":71},[43,390,68],{"class":67},[43,392,77],{"class":49},[43,394,395,397,400,402,404,406,408,411],{"class":45,"line":80},[43,396,83],{"class":53},[43,398,399],{"class":49}," { ref, nextTick } ",[43,401,89],{"class":53},[43,403,92],{"class":67},[43,405,37],{"class":71},[43,407,68],{"class":67},[43,409,410],{"class":49},"; ",[43,412,414],{"class":413},"shSDL","\u002F\u002F Updated import\n",[43,416,417],{"class":45,"line":102},[43,418,106],{"emptyLinePlaceholder":105},[43,420,421,423,425,427,429,431,433,435,437,439,441],{"class":45,"line":109},[43,422,112],{"class":53},[43,424,115],{"class":49},[43,426,64],{"class":53},[43,428,121],{"class":120},[43,430,50],{"class":49},[43,432,127],{"class":126},[43,434,130],{"class":53},[43,436,134],{"class":133},[43,438,137],{"class":49},[43,440,141],{"class":140},[43,442,144],{"class":49},[43,444,445,447,449,451,453,455,457],{"class":45,"line":147},[43,446,112],{"class":53},[43,448,152],{"class":49},[43,450,64],{"class":53},[43,452,121],{"class":120},[43,454,159],{"class":49},[43,456,162],{"class":140},[43,458,144],{"class":49},[43,460,461],{"class":45,"line":167},[43,462,106],{"emptyLinePlaceholder":105},[43,464,465],{"class":45,"line":172},[43,466,467],{"class":413},"\u002F\u002F Function now marked as async\n",[43,469,470,472,474,476,479,481,483],{"class":45,"line":192},[43,471,112],{"class":53},[43,473,177],{"class":120},[43,475,180],{"class":53},[43,477,478],{"class":53}," async",[43,480,183],{"class":49},[43,482,186],{"class":53},[43,484,189],{"class":49},[43,486,487,489,491,493],{"class":45,"line":206},[43,488,195],{"class":49},[43,490,64],{"class":53},[43,492,200],{"class":53},[43,494,203],{"class":49},[43,496,497],{"class":45,"line":211},[43,498,106],{"emptyLinePlaceholder":105},[43,500,501,504,507,510],{"class":45,"line":226},[43,502,503],{"class":53},"  await",[43,505,506],{"class":120}," nextTick",[43,508,509],{"class":49},"(); ",[43,511,512],{"class":413},"\u002F\u002F Notice the new change here\n",[43,514,515],{"class":45,"line":237},[43,516,106],{"emptyLinePlaceholder":105},[43,518,519,521,523,525],{"class":45,"line":243},[43,520,214],{"class":53},[43,522,217],{"class":49},[43,524,220],{"class":53},[43,526,223],{"class":49},[43,528,529,531,533],{"class":45,"line":249},[43,530,229],{"class":49},[43,532,64],{"class":53},[43,534,234],{"class":49},[43,536,537],{"class":45,"line":259},[43,538,240],{"class":49},[43,540,541],{"class":45,"line":264},[43,542,246],{"class":49},[43,544,545,547,549],{"class":45,"line":274},[43,546,252],{"class":49},[43,548,54],{"class":53},[43,550,77],{"class":49},[43,552,553],{"class":45,"line":313},[43,554,106],{"emptyLinePlaceholder":105},[43,556,557,559,561],{"class":45,"line":339},[43,558,50],{"class":49},[43,560,269],{"class":53},[43,562,77],{"class":49},[43,564,566,568,570,572,574,576,578,580,582,584,586,588,590,592,594],{"class":45,"line":565},20,[43,567,277],{"class":49},[43,569,280],{"class":53},[43,571,283],{"class":57},[43,573,64],{"class":53},[43,575,68],{"class":67},[43,577,280],{"class":71},[43,579,68],{"class":67},[43,581,294],{"class":57},[43,583,64],{"class":53},[43,585,68],{"class":67},[43,587,301],{"class":71},[43,589,68],{"class":67},[43,591,306],{"class":49},[43,593,280],{"class":53},[43,595,77],{"class":49},[43,597,599,601,603,605,607,609,611,613,615,617],{"class":45,"line":598},21,[43,600,277],{"class":49},[43,602,318],{"class":53},[43,604,121],{"class":57},[43,606,64],{"class":53},[43,608,68],{"class":67},[43,610,327],{"class":71},[43,612,68],{"class":67},[43,614,332],{"class":49},[43,616,318],{"class":53},[43,618,77],{"class":49},[43,620,622,624,626],{"class":45,"line":621},22,[43,623,252],{"class":49},[43,625,269],{"class":53},[43,627,77],{"class":49},[629,630,631],"style",{},"html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s0Tla, html code.shiki .s0Tla{--shiki-default:#FF79C6}html pre.shiki code .sY_PY, html code.shiki .sY_PY{--shiki-default:#50FA7B;--shiki-default-font-style:italic}html pre.shiki code .seVfx, html code.shiki .seVfx{--shiki-default:#E9F284}html pre.shiki code .s-mGx, html code.shiki .s-mGx{--shiki-default:#F1FA8C}html pre.shiki code .sAOxA, html code.shiki .sAOxA{--shiki-default:#50FA7B}html pre.shiki code .sGEwX, html code.shiki .sGEwX{--shiki-default:#FFB86C;--shiki-default-font-style:italic}html pre.shiki code .sCp4m, html code.shiki .sCp4m{--shiki-default:#8BE9FD;--shiki-default-font-style:italic}html pre.shiki code .sIQBb, html code.shiki .sIQBb{--shiki-default:#BD93F9}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .shSDL, html code.shiki .shSDL{--shiki-default:#6272A4}",{"title":38,"searchDepth":80,"depth":80,"links":633},[],null,"When you need to automatically scroll an element to its bottom after toggling it into view in Vue 3, you would notice things doesn't actually work properly if you call the scroll function immediately after toggling the element's visibility. Let's consider this scenario where you have a scrollable element that is hidden by default and a user has to click on a checkbox to make it visible","md",{"If there is vital information at the bottom of the element, in most cases you want to automatically scroll to the botton as soon as the element is visible":638},[639],[640],{"\" createdAt":22},"\u002Farticles\u002Fscroll-to-bottom-vue-with-hidden-elements",{"title":5,"description":635},"articles\u002F1.scroll-to-bottom-vue-with-hidden-elements","L8Hntp8fRSV-PbA3QyNaUJs05XCHjrIHYFUhYDNHXRo",1785226970010]