स्क्रॉल स्थिति को बनाए रखना केवल तब काम करता है जब संदेशों के निचले भाग के पास नहीं


10

मैं अन्य मोबाइल चैटिंग ऐप्स की नकल करने की कोशिश कर रहा हूं, जब आप send-messageटेक्स्टबॉक्स का चयन करते हैं और यह वर्चुअल कीबोर्ड को खोलता है, तो नीचे-सबसे संदेश अभी भी देखने में है। सीएसएस के साथ आश्चर्यजनक रूप से ऐसा करने का कोई तरीका प्रतीत नहीं होता है, इसलिए जावास्क्रिप्ट resize(केवल यह पता लगाने के लिए कि कीबोर्ड कब खोला गया है और जाहिरा तौर पर बंद है) घटनाओं और मैनुअल स्क्रॉलिंग को बचाव के लिए।

किसी ने प्रदान की इस समाधान और मुझे पता चला इस समाधान , जो दोनों काम करने लगते हैं।

सिवाय एक मामले में। के लिए कुछ कारण है, अगर आप के भीतर हैं MOBILE_KEYBOARD_HEIGHT(मेरे मामले में 250 पिक्सल) संदेशों div के नीचे के पिक्सल, जब आप मोबाइल कीबोर्ड बंद, कुछ अजीब होता है। पूर्व समाधान के साथ, यह नीचे तक स्क्रॉल करता है। और बाद के समाधान के साथ, यह MOBILE_KEYBOARD_HEIGHTनीचे से पिक्सल को स्क्रॉल करता है।

यदि आपको इस ऊंचाई से ऊपर स्क्रॉल किया जाता है, तो ऊपर दिए गए दोनों समाधान त्रुटिपूर्ण रूप से काम करते हैं। यह केवल तभी है जब आप नीचे हैं कि उनके पास यह मामूली समस्या है।

मैंने सोचा कि शायद यह सिर्फ मेरा कार्यक्रम था, जो कुछ अजीब आवारा कोड के साथ ऐसा कर रहा था, लेकिन नहीं, मैंने एक फिडेल को भी पुन: पेश किया और इसका सटीक मुद्दा है। इसे डिबग करने के लिए मेरी क्षमा याचना, लेकिन अगर आप अपने फोन पर https://jsfiddle.net/t596hy8d/6/show (शो प्रत्यय एक पूर्ण-स्क्रीन मोड प्रदान करता है) पर जाते हैं, तो आपको देखने में सक्षम होना चाहिए समान व्यवहार।

यदि आप पर्याप्त स्क्रॉल करते हैं, तो कीबोर्ड खोलना और बंद करना, इस स्थिति को बनाए रखता है। हालाँकि, यदि आप नीचे के पिक्सेल के भीतर कीबोर्ड बंद करते हैं , तो आप MOBILE_KEYBOARD_HEIGHTपाएंगे कि यह नीचे की ओर स्क्रॉल करता है।

यह क्या कारण है?

कोड प्रजनन यहाँ:

window.onload = function(e){ 
  document.querySelector(".messages").scrollTop = 10000;
  
  bottomScroller(document.querySelector(".messages"));
}
  

function bottomScroller(scroller) {
  let scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;

  scroller.addEventListener('scroll', () => { 
  scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
  });   

  window.addEventListener('resize', () => { 
  scroller.scrollTop = scroller.scrollHeight - scrollBottom - scroller.clientHeight;

  scrollBottom = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
  });
}
.container {
  width: 400px;
  height: 87vh;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  overflow-y: auto;
  height: 100%;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}
<div class="container">
  <div class="messages">
  <div class="message">hello 1</div>
  <div class="message">hello 2</div>
  <div class="message">hello 3</div>
  <div class="message">hello 4</div>
  <div class="message">hello 5</div>
  <div class="message">hello 6 </div>
  <div class="message">hello 7</div>
  <div class="message">hello 8</div>
  <div class="message">hello 9</div>
  <div class="message">hello 10</div>
  <div class="message">hello 11</div>
  <div class="message">hello 12</div>
  <div class="message">hello 13</div>
  <div class="message">hello 14</div>
  <div class="message">hello 15</div>
  <div class="message">hello 16</div>
  <div class="message">hello 17</div>
  <div class="message">hello 18</div>
  <div class="message">hello 19</div>
  <div class="message">hello 20</div>
  <div class="message">hello 21</div>
  <div class="message">hello 22</div>
  <div class="message">hello 23</div>
  <div class="message">hello 24</div>
  <div class="message">hello 25</div>
  <div class="message">hello 26</div>
  <div class="message">hello 27</div>
  <div class="message">hello 28</div>
  <div class="message">hello 29</div>
  <div class="message">hello 30</div>
  <div class="message">hello 31</div>
  <div class="message">hello 32</div>
  <div class="message">hello 33</div>
  <div class="message">hello 34</div>
  <div class="message">hello 35</div>
  <div class="message">hello 36</div>
  <div class="message">hello 37</div>
  <div class="message">hello 38</div>
  <div class="message">hello 39</div>
  </div>
  <div class="send-message">
	<input />
  </div>
</div>


मैं ईवेंट हैंडलर को IntersectionObserver और ResizeObserver से बदल दूंगा। इवेंट हैंडलर की तुलना में उनके पास सीपीयू ओवरहेड बहुत कम है। यदि आप पुराने ब्राउज़रों को लक्षित कर रहे हैं, तो दोनों में पॉलीफिल होते हैं।
bigless

क्या आपने मोबाइल उपकरणों के लिए फ़ायरफ़ॉक्स पर यह कोशिश की है? यह इस समस्या है प्रतीत नहीं होता है। हालाँकि, Chrome पर यह कोशिश आपके द्वारा बताई गई समस्या का कारण बनती है।
रिचर्ड

खैर, यह क्रोम वैसे भी काम करने के लिए है। हालांकि यह अच्छा फ़ायरफ़ॉक्स मुद्दा नहीं है।
रयान पेसकेल

मेरी बात को ठीक से न बताने के लिए मेरा बुरा। यदि एक ब्राउज़र में कोई समस्या है और दूसरा नहीं है, तो, IMO का मतलब यह हो सकता है कि आपको अलग-अलग ब्राउज़रों के लिए थोड़ा अलग कार्यान्वयन की आवश्यकता हो सकती है।
रिचर्ड

1
@ अल्फर ठीक है। समझा। अनुस्मारक के लिए धन्यवाद, मुझे लगता है कि अगली बार जब मैं किसी से एक जवाब फिर से पूछने के लिए पूछूंगा।
रिचर्ड

जवाबों:


3

मुझे आखिरकार एक समाधान मिल गया है जो वास्तव में काम करता है। हालांकि यह आदर्श नहीं हो सकता है, यह वास्तव में सभी मामलों में काम करता है। यहाँ कोड है:

bottomScroller(document.querySelector(".messages"));

bottomScroller = scroller => {
  let pxFromBottom = 0;

  let calcPxFromBottom = () => pxFromBottom = scroller.scrollHeight - (scroller.scrollTop + scroller.clientHeight);

  setInterval(calcPxFromBottom, 500);

  window.addEventListener('resize', () => { 
    scroller.scrollTop = scroller.scrollHeight - pxFromBottom - scroller.clientHeight;
  });
}

रास्ते में मेरे पास कुछ कड़ियाँ थीं:

  1. वर्चुअल कीबोर्ड को बंद करते समय, scrollघटना से तुरंत पहले एक घटना होती है resize। यह केवल कीबोर्ड को बंद करते समय होता है, इसे खोलने पर नहीं। यही कारण है कि आप scrollईवेंट को सेट करने के लिए उपयोग नहीं कर सकते pxFromBottom, क्योंकि यदि आप नीचे हैं तो यह scrollईवेंट के ठीक पहले ईवेंट में स्वयं को 0 पर सेट कर देगा resize, गणना को गड़बड़ कर देगा।

  2. एक और कारण है कि सभी समाधानों को संदेश के निचले भाग के पास कठिनाई होती है div समझने के लिए थोड़ा मुश्किल है। उदाहरण के लिए, मेरे आकार बदलने वाले समाधान में मैं scrollTopवर्चुअल कीबोर्ड को खोलते या बंद करते समय 250 (मोबाइल कीबोर्ड ऊंचाई) जोड़ या घटाता हूं । यह नीचे के पास को छोड़कर पूरी तरह से काम करता है। क्यों? क्योंकि मान लीजिए कि आप नीचे से 50 पिक्सेल हैं और कीबोर्ड बंद करते हैं। यह scrollTop(कीबोर्ड ऊंचाई) से 250 घटा देगा , लेकिन इसे केवल 50 घटाना चाहिए! तो यह हमेशा नीचे की ओर कीबोर्ड को बंद करते समय गलत नियत स्थान पर रीसेट हो जाएगा।

  3. मेरा यह भी मानना ​​है कि आप इस समाधान के लिए घटनाओं onFocusऔर onBlurघटनाओं का उपयोग नहीं कर सकते , क्योंकि वे केवल तब होते हैं जब शुरू में कीबोर्ड खोलने के लिए टेक्स्टबॉक्स का चयन करते हैं। आप इन घटनाओं को सक्रिय किए बिना मोबाइल कीबोर्ड को खोलने और बंद करने में पूरी तरह से सक्षम हैं, और जैसे, वे यहां उपयोग करने में सक्षम नहीं हैं।

मेरा मानना ​​है कि उपरोक्त बिंदु एक समाधान विकसित करने के लिए महत्वपूर्ण हैं, क्योंकि वे पहली बार में स्पष्ट नहीं हैं, लेकिन एक मजबूत समाधान को विकसित करने से रोकते हैं।

मुझे यह समाधान पसंद नहीं है (अंतराल थोड़ा अक्षम है और दौड़ की स्थिति के लिए प्रवण है), लेकिन मुझे कुछ भी बेहतर नहीं मिल सकता है जो हमेशा काम करता है।


1

मुझे लगता है कि तुम क्या चाहते हो overflow-anchor

समर्थन बढ़ रहा है, लेकिन कुल नहीं, फिर भी https://caniuse.com/#feat=css-overflow-anchor

इस पर एक CSS- ट्रिक्स लेख से:

स्क्रॉल एंकरिंग पेज पर उपयोगकर्ता की स्थिति को लॉक करके उस "कूद" अनुभव को रोकता है जबकि वर्तमान स्थान के ऊपर डोम में परिवर्तन हो रहे हैं। यह उपयोगकर्ता को एंकर रहने की अनुमति देता है जहां वे पृष्ठ पर हैं, भले ही नए तत्व डोम पर लोड किए गए हैं।

अतिप्रवाह-एंकर संपत्ति हमें उस घटना में स्क्रॉल एंकरिंग सुविधा से ऑप्ट-आउट करने की अनुमति देती है कि तत्वों को लोड किए जाने पर सामग्री को फिर से प्रवाह करने की अनुमति देना पसंद किया जाता है।

यहाँ उनके एक उदाहरण का थोड़ा संशोधित संस्करण है:

let scroller = document.querySelector('#scroller');
let anchor = document.querySelector('#anchor');

// https://ajaydsouza.com/42-phrases-a-lexophile-would-love/
let messages = [
  'I wondered why the baseball was getting bigger. Then it hit me.',
  'Police were called to a day care, where a three-year-old was resisting a rest.',
  'Did you hear about the guy whose whole left side was cut off? He’s all right now.',
  'The roundest knight at King Arthur’s round table was Sir Cumference.',
  'To write with a broken pencil is pointless.',
  'When fish are in schools they sometimes take debate.',
  'The short fortune teller who escaped from prison was a small medium at large.',
  'A thief who stole a calendar… got twelve months.',
  'A thief fell and broke his leg in wet cement. He became a hardened criminal.',
  'Thieves who steal corn from a garden could be charged with stalking.',
  'When the smog lifts in Los Angeles , U. C. L. A.',
  'The math professor went crazy with the blackboard. He did a number on it.',
  'The professor discovered that his theory of earthquakes was on shaky ground.',
  'The dead batteries were given out free of charge.',
  'If you take a laptop computer for a run you could jog your memory.',
  'A dentist and a manicurist fought tooth and nail.',
  'A bicycle can’t stand alone; it is two tired.',
  'A will is a dead giveaway.',
  'Time flies like an arrow; fruit flies like a banana.',
  'A backward poet writes inverse.',
  'In a democracy it’s your vote that counts; in feudalism, it’s your Count that votes.',
  'A chicken crossing the road: poultry in motion.',
  'If you don’t pay your exorcist you can get repossessed.',
  'With her marriage she got a new name and a dress.',
  'Show me a piano falling down a mine shaft and I’ll show you A-flat miner.',
  'When a clock is hungry it goes back four seconds.',
  'The guy who fell onto an upholstery machine was fully recovered.',
  'A grenade fell onto a kitchen floor in France and resulted in Linoleum Blownapart.',
  'You are stuck with your debt if you can’t budge it.',
  'Local Area Network in Australia : The LAN down under.',
  'He broke into song because he couldn’t find the key.',
  'A calendar’s days are numbered.',
];

function randomMessage() {
  return messages[(Math.random() * messages.length) | 0];
}

function appendChild() {
  let msg = document.createElement('div');
  msg.className = 'message';
  msg.innerText = randomMessage();
  scroller.insertBefore(msg, anchor);
}
setInterval(appendChild, 1000);
html {
  height: 100%;
  display: flex;
}

body {
  min-height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  padding: 0;
}

#scroller {
  flex: 2;
}

#scroller * {
  overflow-anchor: none;
}

.new-message {
  position: sticky;
  bottom: 0;
  background-color: blue;
  padding: .2rem;
}

#anchor {
  overflow-anchor: auto;
  height: 1px;
}

body {
  background-color: #7FDBFF;
}

.message {
  padding: 0.5em;
  border-radius: 1em;
  margin: 0.5em;
  background-color: white;
}
<div id="scroller">
  <div id="anchor"></div>
</div>

<div class="new-message">
  <input type="text" placeholder="New Message">
</div>

इसे मोबाइल पर खोलें: https://cdpn.io/chasebank/debug/PowxdOR

जो कर रहा है वह मूल रूप से नए संदेश तत्वों के किसी भी डिफ़ॉल्ट एंकरिंग को अक्षम कर रहा है, के साथ #scroller * { overflow-anchor: none }

और इसके बजाय एक खाली तत्व को लंगर डालना जो #anchor { overflow-anchor: auto }हमेशा उन नए संदेशों के बाद आएगा, क्योंकि नए संदेश इससे पहले डाले जा रहे हैं ।

एंकरिंग में बदलाव को नोटिस करने के लिए एक स्क्रॉल होना चाहिए, जो मुझे लगता है कि आमतौर पर अच्छा यूएक्स है। लेकिन या तो रास्ता, कीबोर्ड खुलने पर वर्तमान स्क्रॉल की स्थिति को बनाए रखा जाना चाहिए।


0

मेरा समाधान सशर्त जाँच के अतिरिक्त आपके प्रस्तावित समाधान के समान है। यहाँ मेरे समाधान का वर्णन है:

  • पिछले स्क्रॉल स्थिति रिकॉर्ड scrollTopऔर पिछले clientHeightके .messagesलिए oldScrollTopऔर oldHeightक्रमशः
  • अद्यतन oldScrollTopऔर oldHeightहर बार एक resizeपर होता है windowऔर अद्यतन oldScrollTopहर बार एक scrollपर होता है.messages
  • जब windowसिकुड़ जाता है (जब वर्चुअल कीबोर्ड दिखाता है), तो ऊँचाई .messagesस्वतः ही वापस आ जाएगी। इरादा व्यवहार .messagesतब भी दिखाई देने वाली बॉटलमॉस्ट सामग्री को तब बनाना है जब .messagesऊँचाई कम हो। यह हमें की जरुरत होती है स्क्रॉल स्थिति को समायोजित करने scrollTopकी .messages
  • जब वर्चुअल कीबोर्ड दिखाता है, तो यह सुनिश्चित करने scrollTopके .messagesलिए अपडेट करें कि .messagesइसकी ऊंचाई घटने से पहले बॉटलमॉस्ट भाग अभी भी दिखाई देता है
  • जब वर्चुअल कीबोर्ड छिप जाता है, तो यह सुनिश्चित करने scrollTopके .messagesलिए अपडेट करें कि ऊंचाई .messagesके .messagesबाद बॉटलस्टोस्ट का हिस्सा सबसे नीचे का हिस्सा है (जब तक कि विस्तार ऊपर की तरफ नहीं हो सकता है; ऐसा तब होता है जब आप लगभग सबसे ऊपर होते हैं .messages)

क्या समस्या हुई?

मेरा (आरंभिक रूप से त्रुटिपूर्ण) तार्किक सोच है: resizeहोता है, .messages'ऊँचाई बदलती है, .messages scrollTopहमारे resizeईवेंट हैंडलर के अंदर अद्यतन होता है । हालाँकि, .messages'ऊंचाई विस्तार पर, एक scrollघटना उत्सुकता से पहले होती है resize! और इससे भी अधिक उत्सुक, scrollघटना केवल तब होती है जब हम कीबोर्ड को छिपाते हैं जब हमने पीछे हटने के अधिकतम scrollTopमूल्य से ऊपर स्क्रॉल किया होता .messagesहै। मेरे मामले में, इसका मतलब यह है कि जब मैं नीचे स्क्रॉल करता हूं 270.334px( कीबोर्ड scrollTopसे पहले अधिकतम .messagesवापस लिया जाता है) और कीबोर्ड को छिपाता है, तो घटना होने scrollसे पहले अजीब resizeऔर आपके .messagesबिल्कुल स्क्रॉल करता है 270.334px। यह स्पष्ट रूप से ऊपर हमारे समाधान को गड़बड़ करता है।

सौभाग्य से, हम इसके आसपास काम कर सकते हैं। इस घटना के होने से scrollपहले मेरी व्यक्तिगत कटौती, resizeक्योंकि ऊँचाई में फैलने पर ऊपर की .messagesअपनी scrollTopस्थिति को बनाए नहीं रख सकता है (यही कारण है कि मैंने उल्लेख किया है कि मेरी प्रारंभिक तार्किक सोच त्रुटिपूर्ण है; सिर्फ इसलिए कि इसकी अधिकतम स्थिति के ऊपर अपनी स्थिति बनाए रखने का कोई तरीका नहीं है; मूल्य) । इसलिए, यह तुरंत इसे अधिकतम मूल्य पर सेट कर सकता है जो इसे दे सकता है (जो कि, आश्चर्यजनक रूप से )।270.334px.messagesscrollTopscrollTop270.334px

हम क्या कर सकते है?

क्योंकि हम केवल oldHeightआकार बदलने पर अपडेट करते हैं, हम जांच सकते हैं कि क्या यह जबरन स्क्रॉल (या अधिक सही ढंग से resize) होता है और यदि ऐसा होता है, तो अपडेट न करें oldScrollTop(क्योंकि हमने पहले ही इसे संभाल लिया है resize!) हमें बस तुलना करने की आवश्यकता है oldHeightऔर वर्तमान ऊंचाई पर। scrollदेखना है कि क्या यह जबरन स्क्रॉल होता है। यह काम करता है क्योंकि oldHeightचालू ऊँचाई के बराबर नहीं होने की स्थिति scrollकेवल तभी सच होगी जब resizeऐसा होता है (जो संयोगवश उस समय स्क्रॉल करने पर मजबूर होता है)।

यहाँ कोड (JSFiddle में) नीचे दिया गया है:

window.onload = function(e) {
  let messages = document.querySelector('.messages')
  messages.scrollTop = messages.scrollHeight - messages.clientHeight
  bottomScroller(messages);
}


function bottomScroller(scroller) {
  let oldScrollTop = scroller.scrollTop
  let oldHeight = scroller.clientHeight

  scroller.addEventListener('scroll', e => {
    console.log(`Scroll detected:
      old scroll top = ${oldScrollTop},
      old height = ${oldHeight},
      new height = ${scroller.clientHeight},
      new scroll top = ${scroller.scrollTop}`)
    if (oldHeight === scroller.clientHeight)
      oldScrollTop = scroller.scrollTop
  });

  window.addEventListener('resize', e => {
    let newScrollTop = oldScrollTop + oldHeight - scroller.clientHeight

    console.log(`Resize detected:
      old scroll top = ${oldScrollTop},
      old height = ${oldHeight},
      new height = ${scroller.clientHeight},
      new scroll top = ${newScrollTop}`)
    scroller.scrollTop = newScrollTop
    oldScrollTop = newScrollTop
    oldHeight = scroller.clientHeight
  });
}
.container {
  width: 400px;
  height: 87vh;
  border: 1px solid #333;
  display: flex;
  flex-direction: column;
}

.messages {
  overflow-y: auto;
  height: 100%;
}

.send-message {
  width: 100%;
  display: flex;
  flex-direction: column;
}
<div class="container">
  <div class="messages">
    <div class="message">hello 1</div>
    <div class="message">hello 2</div>
    <div class="message">hello 3</div>
    <div class="message">hello 4</div>
    <div class="message">hello 5</div>
    <div class="message">hello 6 </div>
    <div class="message">hello 7</div>
    <div class="message">hello 8</div>
    <div class="message">hello 9</div>
    <div class="message">hello 10</div>
    <div class="message">hello 11</div>
    <div class="message">hello 12</div>
    <div class="message">hello 13</div>
    <div class="message">hello 14</div>
    <div class="message">hello 15</div>
    <div class="message">hello 16</div>
    <div class="message">hello 17</div>
    <div class="message">hello 18</div>
    <div class="message">hello 19</div>
    <div class="message">hello 20</div>
    <div class="message">hello 21</div>
    <div class="message">hello 22</div>
    <div class="message">hello 23</div>
    <div class="message">hello 24</div>
    <div class="message">hello 25</div>
    <div class="message">hello 26</div>
    <div class="message">hello 27</div>
    <div class="message">hello 28</div>
    <div class="message">hello 29</div>
    <div class="message">hello 30</div>
    <div class="message">hello 31</div>
    <div class="message">hello 32</div>
    <div class="message">hello 33</div>
    <div class="message">hello 34</div>
    <div class="message">hello 35</div>
    <div class="message">hello 36</div>
    <div class="message">hello 37</div>
    <div class="message">hello 38</div>
    <div class="message">hello 39</div>
  </div>
  <div class="send-message">
    <input />
  </div>
</div>

मोबाइल के लिए फ़ायरफ़ॉक्स और क्रोम पर परीक्षण किया गया है और यह दोनों ब्राउज़रों के लिए काम करता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.