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

La idea aquí es hacer que .messages-container ocupe toda la pantalla que pueda. Dentro de .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 al final de la conversación.
  2. El .text-input, dinámicamente se hace más grande

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

Una forma de solucionarlo, si usamos react, calcule la altura de la entrada de texto y, si algo cambia, informe a .messages-container

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

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

¿Hay una mejor manera? ¿Podría usar css de tal manera que exprese que cuando .text-input-aumenta, quiero esencialmente shift up todos los .messages-container

39
Stepan Parunashvili 11 dic. 2015 a las 01:55

6 respuestas

La mejor respuesta

2: nd revisión de esta respuesta

Tu amigo aquí es flex-direction: column-reverse;, que hace todo lo que pides mientras alineas los mensajes en la parte inferior del contenedor de mensajes, como por ejemplo Skype y muchas otras aplicaciones de chat.

.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%; }

La desventaja de flex-direction: column-reverse; es un error en IE / Edge / Firefox, donde no se muestra la barra de desplazamiento, sobre la cual puede leer más aquí: Flexbox column-reverse y overflow en Firefox / IE

Lo bueno es que tienes ~ 90% de soporte de navegador en dispositivos móviles / tabletas y ~ 65% para computadora de escritorio, y contando a medida que el error se soluciona, ... y hay una solución alternativa.

// 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 fragmento de código a continuación, he agregado las 2 funciones de arriba, para que IE / Edge / Firefox se comporte de la misma manera que 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 al margen 1: El método de detección no se ha probado completamente, pero debería funcionar en los navegadores más nuevos.

Nota al margen 2: Adjuntar un controlador de eventos de cambio de tamaño para la entrada de chat podría ser más eficiente que llamar a la función updateScroll.

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

24
Community 23 may. 2017 a las 12:25

He movido text-input dentro de messages, lo he posicionado en la parte inferior del contenedor y le he dado a messages suficiente relleno inferior para que quede espacio 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 utilizando 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 desplazamiento sale de la parte inferior nuevamente, 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
Jamie Barker 17 dic. 2015 a las 14:59

Intente con el siguiente violín: https://jsfiddle.net/Hazardous/bypxg25c/. Aunque el violín actualmente está usando jQuery para aumentar / cambiar el tamaño del área de texto, el quid está en los estilos relacionados con la flexibilidad utilizados para las clases de contenedor de mensajes y contenedor de entrada:

.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 de contracción flexible se establece en 1 para .messages-container y 0 para .input-container. Esto garantiza que el contenedor de mensajes se reduzca cuando haya una reasignación de tamaño.

2
hazardous 18 dic. 2015 a las 08:23

Usted escribe;

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 el lugar lógico para activar this.props.onResize ().

0
J. Mark Stevens 21 dic. 2015 a las 20:44

A quien le interese,

Las respuestas anteriores no fueron suficientes para mi pregunta.

La solución que encontré fue hacer que la variable innerWidth y innerHeight sea constante, ya que el innerWidth del navegador cambia en el desplazamiento para adaptarse a la barra de desplazamiento.

var innerWidth = window.innerWidth
var innerHeight = window.innerHeight

OR FOR REACT

this.setState({width: window.innerWidth, height: window.innerHeight})

En otras palabras, para ignorarlo, debe hacer que todo sea constante como si nunca se estuviera desplazando. ¡Recuerde actualizarlos en Cambio de tamaño / orientación!

Oscar

-1
odlh 21 oct. 2019 a las 15:38

Solo necesita un conjunto de reglas CSS:

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

Eso es todo, ya terminaste!

Cómo funciona: Primero, voltea verticalmente el elemento contenedor para que la parte superior se convierta en la parte inferior (dándonos la orientación de desplazamiento deseada), luego voltea el elemento de contenido para que los mensajes no estén al revés abajo.

Este enfoque funciona en todos los navegadores modernos. Sin embargo, tiene un efecto secundario extraño: cuando usa una rueda del mouse en el cuadro de mensaje, la dirección de desplazamiento se invierte. Esto se puede solucionar con unas pocas líneas de JavaScript, como se muestra a continuación.

Aquí hay una demostración y un violín 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>
12
DoctorDestructo 13 abr. 2019 a las 11:52