Div desplazable para pegarse a la parte inferior, cuando el div exterior cambia de tamaño


Aquí hay un ejemplo de aplicación de chat - >

La idea aquí es que el .messages-container ocupe tanto de la pantalla como pueda. Dentro .messages-container, .scroll contiene la lista de mensajes, y en caso de que haya más mensajes que el tamaño de la pantalla, se desplaza.

Ahora, considere este caso:

  1. El usuario se desplaza hasta la parte inferior de la conversación
  2. El .text-input, dinámicamente se hace más grande

Ahora, en lugar de que el usuario permanezca desplazado a la al final de la conversación, la entrada de texto aumenta, y ya no ven la parte inferior.

Una forma de arreglarlo, si estamos usando react, calcular la altura de entrada de texto, y si algo cambia, vamos .mensajes-contenedor saber

componentDidUpdate() {
  window.setTimeout(_ => {
    const newHeight = this.calcHeight();
    if (newHeight !== this._oldHeight) {
      this.props.onResize();
    }
    this._oldHeight = newHeight;
  });
}

Pero, esto causa problemas de rendimiento visibles, y es triste pasar mensajes como este.

Hay una manera mejor? Podría usar css de tal manera, para expresar eso cuando .text-input-increases, quiero esencialmente shift up .mensajes-contenedor

Author: Vadim Ovchinnikov, 2015-12-11

5 answers

2:nd revisión de esta respuesta

Su amigo aquí es flex-direction: column-reverse; que hace todo lo que pide mientras alinea los mensajes en la parte inferior del contenedor de mensajes, al igual que por ejemplo Skype y muchas otras aplicaciones de chat lo hacen.

.chat-window{
  display:flex;
  flex-direction:column;
  height:100%;
}
.chat-messages{
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;
}

.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }

El inconveniente con flex-direction: column-reverse; es un error en IE / Edge / Firefox, donde la barra de desplazamiento no se muestra, que puede leer más aquí: Columna Flexbox-invertir y desbordamiento en Firefox / IE

La ventaja es que tiene ~ 90% navegador soporte en móviles / tabletas y ~ 65% para escritorio, y contando a medida que se corrige el error,...y hay una solución.

// scroll to bottom
function updateScroll(el){
  el.scrollTop = el.scrollHeight;
}
// only shift-up if at bottom
function scrollAtBottom(el){
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}

En el siguiente fragmento de código he agregado las 2 funciones de arriba, para hacer que IE/Edge/Firefox se comporte de la misma manera que lo hace flex-direction: column-reverse;.

function addContent () {
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.value.length > 0) {
    msgdiv.innerHTML += msgtxt.value + '<br/>';
    msgtxt.value = "";
  } else {
    msgdiv.innerHTML += 'Long long content ' + (tempCounter++) + '!<br/>';
  }
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) {
    updateScroll(msgdiv);
  }
}

function resizeInput () {
  var msgdiv = document.getElementById('messages');
  var msgtxt = document.getElementById('inputs');
  var atbottom = scrollAtBottom(msgdiv);

  if (msgtxt.style.height == '120px') {
    msgtxt.style.height = 'auto';
  } else {
    msgtxt.style.height = '120px';
  }
  
  /* if at bottom and is IE/Edge/Firefox */
  if (atbottom && (!isWebkit || isEdge)) {
    updateScroll(msgdiv);
  }
}


/* fix for IE/Edge/Firefox */
var isWebkit = ('WebkitAppearance' in document.documentElement.style);
var isEdge = ('-ms-accelerator' in document.documentElement.style);
var tempCounter = 6;

function updateScroll(el){
  el.scrollTop = el.scrollHeight;
}
function scrollAtBottom(el){
  return (el.scrollTop + 5 >= (el.scrollHeight - el.offsetHeight));
}
html, body { height:100%; margin:0; padding:0; }

.chat-window{
  display:flex;
  flex-direction:column;
  height:100%;
}
.chat-messages{
  flex: 1;
  height:100%;
  overflow: auto;
  display: flex;
  flex-direction: column-reverse;
}

.chat-input { border-top: 1px solid #999; padding: 20px 5px }
.chat-input-text { width: 60%; min-height: 40px; max-width: 60%; }


/* temp. buttons for demo */
button { width: 12%; height: 44px; margin-left: 5%; vertical-align: top; }

/* begin - fix for hidden scrollbar in IE/Edge/Firefox */
.chat-messages-text{ overflow: auto; }
@media screen and (-webkit-min-device-pixel-ratio:0) {
  .chat-messages-text{ overflow: visible; }
  /*  reset Edge as it identifies itself as webkit  */
  @supports (-ms-accelerator:true) { .chat-messages-text{ overflow: auto; } }
}
/* hide resize FF */
@-moz-document url-prefix() { .chat-input-text { resize: none } }
/* end - fix for hidden scrollbar in IE/Edge/Firefox */
<div class="chat-window">
  <div class="chat-messages">
    <div class="chat-messages-text" id="messages">
      Long long content 1!<br/>
      Long long content 2!<br/>
      Long long content 3!<br/>
      Long long content 4!<br/>
      Long long content 5!<br/>
    </div>
  </div>
  <div class="chat-input">
    <textarea class="chat-input-text" placeholder="Type your message here..." id="inputs"></textarea>
    <button onclick="addContent();">Add msg</button>
    <button onclick="resizeInput();">Resize input</button>
  </div>
</div>

Nota lateral 1: El método de detección no está completamente probado, pero debería funcionar en navegadores más nuevos.

Nota lateral 2: Adjuntar un manejador de eventos de redimensionamiento para la entrada de chat más eficiente que llamar a la función updateScroll.

Nota: Créditos a HaZardouS por reutilizar su estructura html

 16
Author: LGSon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:25:58

Solo necesita un conjunto de reglas CSS:

.messages-container, .scroll {transform: scale(1,-1);}

Eso es todo, ¡ya está! Eso mantendrá la conversación desplazada hasta la parte inferior, con el último mensaje a la vista, independientemente de cualquier cambio de tamaño del contenedor, caja de entrada o cualquier otra cosa. Funciona en todos los navegadores modernos.

Cómo funciona: Primero, voltea verticalmente el elemento externo messages-container (que maneja el desplazamiento) para que la parte superior se convierta en la parte inferior. Luego voltea el elemento interno scroll para que los mensajes no estará al revés.

Este enfoque tiene un efecto secundario extraño: cuando se utiliza una rueda del ratón (o algo que emula una) en el cuadro de mensaje, la dirección de desplazamiento se invierte. Si eso te molesta, puedes arreglarlo con JavaScript (como se muestra a continuación).

Aquí hay una demostración y un jsfiddle para jugar:

//Reverse wheel direction
document.querySelector('.messages-container').addEventListener('wheel', function(e) {
  if(e.deltaY) {
    e.preventDefault();
    e.currentTarget.scrollTop -= parseFloat(getComputedStyle(e.currentTarget).getPropertyValue('font-size')) * (e.deltaY < 0 ? -1 : 1) * 2;
  }
});

//The rest of the JS just handles the test buttons and is not part of the solution
send = function() {
  var inp = document.querySelector('.text-input');
  document.querySelector('.scroll').insertAdjacentHTML('beforeend', '<p>' + inp.value);
  inp.value = '';
  inp.focus();
}
resize = function() {
  var inp = document.querySelector('.text-input');
  inp.style.height = inp.style.height === '50%' ? null : '50%';
}
html,body {height: 100%;margin: 0;}
.conversation {
  display: flex;
  flex-direction: column;
  height: 100%;
}
.messages-container {
  flex-shrink: 10;
  height: 100%;
  overflow: auto;
}
.messages-container, .scroll {transform: scale(1,-1);}
.text-input {resize: vertical;}
<div class="conversation">
  <div class="messages-container">
    <div class="scroll">
      <p>Message 1<p>Message 2<p>Message 3<p>Message 4<p>Message 5
      <p>Message 6<p>Message 7<p>Message 8<p>Message 9<p>Message 10
    </div>
  </div>
  <textarea class="text-input" autofocus>Your message</textarea>
  <div>
    <button id="send" onclick="send();">Send input</button>
    <button id="resize" onclick="resize();">Resize input box</button>
  </div>
</div>
 9
Author: DoctorDestructo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-21 19:31:58

Por favor, intente el siguiente violín - https://jsfiddle.net/Hazardous/bypxg25c/. Aunque el fiddle está usando actualmente jQuery para aumentar/redimensionar el área de texto, el quid está en los estilos relacionados con flex utilizados para las clases messages-container y input-container -

.messages-container{
  order:1;
  flex:0.9 1 auto;
  overflow-y:auto;
  display:flex;
  flex-direction:row;
  flex-wrap:nowrap;
  justify-content:flex-start;
  align-items:stretch;
  align-content:stretch;
}

.input-container{
  order:2;
  flex:0.1 0 auto;
}

El valor flex-shrink se establece en 1 for.mensajes-contenedor y 0 para .contenedor de entrada. Esto asegura que el contenedor de mensajes se reduzca cuando hay una reasignación de tamaño.

 2
Author: hazardous,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-18 08:23:30

He movido text-input dentro de messages, absolute lo posicionó en la parte inferior del contenedor y le di messages suficiente relleno inferior para espaciar en consecuencia.

Ejecute algún código para agregar una clase a conversation, que cambia la altura de text-input y el relleno inferior de messages usando una agradable animación de transición CSS.

El JavaScript ejecuta una función "scrollTo" al mismo tiempo que se ejecuta la transición CSS para mantener el desplazamiento en la parte inferior.

Cuando el pergamino sale de la parte inferior una vez más, eliminamos la clase de conversation

Espero que esto ayude.

Https://jsfiddle.net/cnvzLfso/5 /

var doScollCheck = true;
var objConv = document.querySelector('.conversation');
var objMessages = document.querySelector('.messages');
var objInput = document.querySelector('.text-input');

function scrollTo(element, to, duration) {
  if (duration <= 0) {
    doScollCheck = true;
    return;
  }
  var difference = to - element.scrollTop;
  var perTick = difference / duration * 10;

  setTimeout(function() {
    element.scrollTop = element.scrollTop + perTick;
    if (element.scrollTop === to) {
      doScollCheck = true;
      return;
    }
    scrollTo(element, to, duration - 10);
  }, 10);
}

function resizeInput(atBottom) {
  var className = 'bigger',
    hasClass;
  if (objConv.classList) {
    hasClass = objConv.classList.contains(className);
  } else {
    hasClass = new RegExp('(^| )' + className + '( |$)', 'gi').test(objConv.className);
  }
  if (atBottom) {
    if (!hasClass) {
      doScollCheck = false;
      if (objConv.classList) {
        objConv.classList.add(className);
      } else {
        objConv.className += ' ' + className;
      }
      scrollTo(objMessages, (objMessages.scrollHeight - objMessages.offsetHeight) + 50, 500);
    }
  } else {
    if (hasClass) {
      if (objConv.classList) {
        objConv.classList.remove(className);
      } else {
        objConv.className = objConv.className.replace(new RegExp('(^|\\b)' + className.split(' ').join('|') + '(\\b|$)', 'gi'), ' ');
      }
    }
  }
}

objMessages.addEventListener('scroll', function() {
  if (doScollCheck) {
    var isBottom = ((this.scrollHeight - this.offsetHeight) === this.scrollTop);
    resizeInput(isBottom);
  }
});
html,
body {
  height: 100%;
  width: 100%;
  background: white;
}
body {
  margin: 0;
  padding: 0;
}
.conversation {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  height: 100%;
  position: relative;
}
.messages {
  overflow-y: scroll;
  padding: 10px 10px 60px 10px;
  -webkit-transition: padding .5s;
  -moz-transition: padding .5s;
  transition: padding .5s;
}
.text-input {
  padding: 10px;
  -webkit-transition: height .5s;
  -moz-transition: height .5s;
  transition: height .5s;
  position: absolute;
  bottom: 0;
  height: 50px;
  background: white;
}
.conversation.bigger .messages {
  padding-bottom: 110px;
}
.conversation.bigger .text-input {
  height: 100px;
}
.text-input input {
  height: 100%;
}
<div class="conversation">
  <div class="messages">
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is a message content
    </p>
    <p>
      This is the last message
    </p>
    <div class="text-input">
      <input type="text" />
    </div>
  </div>
</div>
 1
Author: Jamie Barker,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-17 14:59:03

Escribes;

Now, consider this case:

    The user scrolls to the bottom of the conversation
    The .text-input, dynamically gets bigger

No sería el método que establece dinámicamente el .text-input es el lugar lógico para disparar esto.apoyos.onResize().

 0
Author: J. Mark Stevens,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-21 20:44:33