Kaydırma konumunun korunması yalnızca mesajlar div'in altına yakın olmadığında çalışır


10

send-messageMetin kutusunu seçtiğinizde ve sanal klavyeyi açtığınızda, en alttaki mesaj hala görüntülenmekte olan diğer mobil sohbet uygulamalarını taklit etmeye çalışıyorum . Bunu CSS ile inanılmaz bir şekilde yapmanın bir yolu yok gibi görünüyor, bu nedenle JavaScript resize(klavyenin açılıp kapatıldığı zaman öğrenmenin tek yolu) olaylar ve kurtarmaya manuel kaydırma.

Birisi bu çözümü sağladı ve her ikisinin de işe yaradığı görülen bu çözümü buldum .

Bir durum hariç. Herhangi bir nedenle, MOBILE_KEYBOARD_HEIGHTmesajlar div'in altındaki piksellerde (benim durumumda 250 piksel), mobil klavyeyi kapattığınızda garip bir şey olur. Önceki çözümle, aşağı doğru kayar. İkinci çözümle, MOBILE_KEYBOARD_HEIGHTpikselleri alttan yukarı kaydırır .

Bu yüksekliğin üzerine kaydırılırsanız, yukarıda verilen her iki çözüm de kusursuz çalışır. Sadece altta olduğunuzda bu küçük sorunu yaşıyorlar.

Belki de sadece garip bir sokak koduyla buna neden olan benim programım olduğunu düşündüm, ama hayır, bir keman bile ürettim ve bu tam bir sorun var. Bunu hata ayıklamak için çok zor yaptığım için özür dilerim, ancak telefonunuzda https://jsfiddle.net/t596hy8d/6/show (show soneki tam ekran modu sağlar) adresine giderseniz, aynı davranış.

Bu davranış, eğer yeterince yukarı kaydırırsanız, klavyeyi açıp kapatarak konumu korur. Ancak, klavyeyi alttan pikseller içinde kapatırsanız , MOBILE_KEYBOARD_HEIGHTbunun yerine alt kısma kaydırıldığını görürsünüz.

Buna ne sebep oluyor?

Kod üretimi:

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>


Olay işleyicilerini IntersectionObserver ve ResizeObserver ile değiştirirdim. Olay işleyicilerinden çok daha düşük CPU ek yükü vardır. Eski tarayıcıları hedefliyorsanız, ikisinde birden çok dolgu bulunur.
bigless

Mobil cihazlar için Firefox'ta bunu denediniz mi? Bu problemi yok gibi görünüyor. Ancak, bunu Chrome'da denemek bahsettiğiniz soruna neden olur.
Richard

Zaten Chrome üzerinde çalışması gerekiyor. Firefox'un sorunu olmayan güzel.
Ryan Peschel

Benim hakkımı düzgün bir şekilde aktarmadığım için kötüyüm. Bir tarayıcı bir sorunu var ve başka, bu, IMO, yoksa belki bunu demek olabilir farklı tarayıcılar için biraz farklı uygulama olması gerekir.
Richard

1
@ halfer Alright. Anlıyorum. Hatırlatma için teşekkür ederim, bir dahaki sefere bir yanıtını tekrar istemesini istediğimde bunu dikkate alacağım.
Richard

Yanıtlar:


3

Sonunda gerçekten işe yarayan bir çözüm buldum . İdeal olmasa da, aslında her durumda çalışır. İşte kod:

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;
  });
}

Yol boyunca sahip olduğum bazı epifaniler:

  1. Sanal klavyeyi kapatırken, scrollolaydan hemen önce bir olay meydana gelir resize. Bu sadece klavyeyi kapatırken değil, açarken gerçekleşir. Bu size kullanamaz nedeni scrollsetine olay pxFromBottomdibe yakın ise o 0'a kendisini ayarlayacaktır, çünkü scrollhemen önce olay resizehesaplama karıştırıyor, olay.

  2. Tüm çözümlerin, div mesajlarının alt kısmında zorlanmasının başka bir nedeni de anlaşılması biraz zor. Örneğin, yeniden boyutlandırma çözümümde scrollTopsanal klavyeyi açarken veya kapatırken 250 (mobil klavye yüksekliği) ekler veya çıkarırım . Bu, dibine yakın olmak dışında mükemmel çalışır. Neden? Çünkü diyelim ki alttan 50 pikselsiniz ve klavyeyi kapatıyorsunuz. scrollTop(Klavye yüksekliği) 250 çıkarır , ancak sadece 50 çıkarır! Bu nedenle, klavyeyi alt tarafa yakın kapatırken her zaman yanlış sabit konuma sıfırlanır.

  3. Ayrıca bu çözümü kullanamayacağınıza onFocusve onBlurolayları kullanamayacağınıza da inanıyorum , çünkü bunlar sadece klavyeyi açmak için metin kutusunu ilk seçerken ortaya çıkıyor. Bu etkinlikleri etkinleştirmeden mobil klavyeyi mükemmel bir şekilde açıp kapatabilirsiniz ve bu nedenle burada kullanılamazlar.

Yukarıdaki noktaların bir çözüm geliştirmek için önemli olduğuna inanıyorum, çünkü başlangıçta açık değiller, ancak sağlam bir çözümün gelişmesini önlüyorlar.

Bu çözümü sevmiyorum (aralık biraz verimsiz ve yarış koşullarına yatkın), ancak her zaman işe yarayacak daha iyi bir şey bulamıyorum.


1

Bence ne istiyorsun overflow-anchor

Destek artıyor, ancak toplam değil, https://caniuse.com/#feat=css-overflow-anchor

Üzerindeki bir CSS-Tricks makalesinden:

Kaydırma Sabitleme, geçerli konumun üzerindeki DOM'da değişiklikler yapılırken kullanıcının sayfadaki konumunu kilitleyerek "atlama" deneyimini önler. Bu, DOM'ya yeni öğeler yüklendiğinde bile kullanıcının sayfada olduğu yerde sabit kalmasını sağlar.

Overflow-anchor özelliği, içeriğin öğeler yüklendikçe yeniden akışına izin verilmesinin tercih edilmesi durumunda Kaydırma Sabitleme özelliğinden çıkmamıza olanak tanır.

Örneklerinden birinin biraz değiştirilmiş bir sürümü:

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>

Mobil cihazda bunu açın: https://cdpn.io/chasebank/debug/PowxdOR

Yaptığı şey, temel olarak yeni mesaj öğelerinin varsayılan sabitlemesini devre dışı bırakmaktır. #scroller * { overflow-anchor: none }

Ve bunun yerine boş bir öğesi ankraj #anchor { overflow-anchor: auto }yeni mesajlar sokulan ediliyor beri hep bu yeni mesajlardan sonra gelecek daha önce buna.

Genelde iyi bir UX olduğunu düşündüğüm ankrajda bir değişiklik fark etmek için bir kaydırma olmalı. Ancak her iki durumda da, klavye açıldığında geçerli kaydırma konumu korunmalıdır.


0

Çözümüm, koşullu kontrolün eklenmesiyle önerilen çözümle aynı. İşte benim çözüm açıklaması:

  • Geçen kaydırma konumunu kaydedin scrollTopve son clientHeightbir .messagesetmek oldScrollTopve oldHeightsırasıyla
  • Güncelleme oldScrollTopve oldHeightbir her zaman resizeolur windowve güncelleştirmek oldScrollTopbir her zaman scrollolur.messages
  • Ne zaman window(zaman sanal klavye gösterileri), sıkıştırıldığı, yüksekliği .messagesotomatik olarak geri çekilecektir. Amaçlanan davranış, en yüksek içeriğin 'yükseklik geri çekilse .messagesbile hala görünür olmasını sağlamaktır .messages. Bu elle kaydırma konumunu ayarlamak etmemizi gerektirir scrollToparasında .messages.
  • Ne zaman sanal klavye gösterileri, güncelleme scrollTopve .messagesen alt kısmı olduğundan emin olmak için .messageskendi yükseklik geri çekme gerçekleşmeden önce hala görülebilir
  • Sanal klavye derileri, güncelleme zaman scrollTopiçinde .messagesemin olmak için bu en alt kısmı .messageskalıntılarının en alt kısmı .messagesyüksekliği genişleme sonrasında (genişleme yukarı olamaz sürece, siz üstündeki neredeyse olduğunuzda Böyle .messages)

Soruna ne sebep oldu?

(Muhtemelen ilk kusurlu) mantıksal düşüncem şudur: resizeolur, .messages'yükseklik değişiklikleri, güncelleme açık olay işleyicimizin .messages scrollTopiçinde olur resize. Ancak, .messages'yükseklik genişlemesi üzerine , scrollmerakla önce bir olay olur resize! Ve daha da meraklı olan scrollolay, yalnızca klavyenin gizlenmediği zamanın maksimum scrollTopdeğerinin üzerine geldiğimizde gizlendiğimizde olur .messages. Aşağıda kaydırırken Benim durumumda, bu araçlar o 270.334px(maksimum scrollTopönce .messagesgarip olduğunu, geri çekilir) ve klavyeyi gizlemek scrollönce resizeolay olur ve sizin kayar .messagestam için 270.334px. Bu açıkça yukarıdaki çözümümüzü berbat ediyor.

Neyse ki, bu sorunu çözebiliriz. Olaydan scrollönce bunun neden kişisel çıkarımım, yükseklikte genişlediğinde yukarıdaki konumunu koruyamamasıdır (bu yüzden ilk mantıksal düşüncemin kusurlu olduğundan bahsettim; sadece konumunu maksimum değerinin üzerinde tutmanın bir yolu olmadığı için değeri) . Bu nedenle, derhal verebileceği maksimum değere (şaşırtıcı olmayan bir şekilde ) ayarlar .resize.messagesscrollTop270.334px.messagesscrollTopscrollTop270.334px

Ne yapabiliriz?

Yalnızca oldHeightyeniden boyutlandırmada güncelleme yaptığımız için , bu zorunlu kaydırmanın (veya daha doğru bir şekilde resize) gerçekleşip gerçekleşmediğini kontrol edebiliriz ve gerçekleşirse, güncelleme yapmayın oldScrollTop(çünkü bunu zaten ele resizealdık!) Sadece karşılaştırmamız oldHeightve mevcut yüksekliği scrollbu zorunlu kaydırmanın gerçekleşip gerçekleşmediğini görmek için. Bu oldHeight, geçerli yüksekliğe eşit olmama koşulu scrollsadece resizegerçekleştiğinde (bu zorunlu kaydırma gerçekleştiğinde rastlantısal olarak) doğru olacağı için çalışır .

Aşağıdaki kod (JSFiddle'da) :

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>

Mobil cihazlar için Firefox ve Chrome'da test edildi ve her iki tarayıcı için de çalışıyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.