【问题标题】:Auto-expanding textarea自动扩展文本区域
【发布时间】:2011-12-06 10:34:47
【问题描述】:

我正在尝试做一个简单的自动扩展文本区域。这是我的代码:

textarea.onkeyup = function () {
  textarea.style.height = textarea.clientHeight + 'px';
}

但是当你输入时,文本区域会无限增长......

我知道有 Dojo 和一个 jQuery 插件,但我宁愿不必使用它们。我查看了他们的实现,最初使用的是scrollHeight,但它做了同样的事情。

您可以开始回答并使用文本区域来播放您的答案。

【问题讨论】:

标签: javascript


【解决方案1】:

在使用scrollHeight 正确扩展/缩小文本区域之前重置高度。 Math.min() 可用于设置文本区域的高度限制。

代码:

var textarea = document.getElementById("textarea");
var heightLimit = 200; /* Maximum height: 200px */

textarea.oninput = function() {
  textarea.style.height = ""; /* Reset the height*/
  textarea.style.height = Math.min(textarea.scrollHeight, heightLimit) + "px";
};

小提琴:http://jsfiddle.net/gjqWy/155

注意:IE8 及更早版本不支持input event。如果您也想支持这个古老的浏览器,请使用keydownkeyuponpaste 和/或oncut

【讨论】:

  • +1:很好...我一直在寻找一种可靠的方法来做到这一点。这是否也考虑了细微差别,例如粘贴到文本区域等?
  • 仅供参考 - 我发布了一个解决方案,允许您按行而不是像素进行限制:stackoverflow.com/a/24824750/126574
【解决方案2】:

我希望自动扩展区域受行数限制(例如 5 行)。我考虑过使用“em”单位,但对于 Rob's solution,这是 error-prone,不会考虑填充等内容。

这就是我想出的:

var textarea = document.getElementById("textarea");
var limitRows = 5;
var messageLastScrollHeight = textarea.scrollHeight;

textarea.oninput = function() {
    var rows = parseInt(textarea.getAttribute("rows"));
    // If we don't decrease the amount of rows, the scrollHeight would show the scrollHeight for all the rows
    // even if there is no text.
    textarea.setAttribute("rows", "1");

    if (rows < limitRows && textarea.scrollHeight > messageLastScrollHeight) {
        rows++;
    } else if (rows > 1 && textarea.scrollHeight < messageLastScrollHeight) {
        rows--;
    }

    messageLastScrollHeight = textarea.scrollHeight;
    textarea.setAttribute("rows", rows);
};

小提琴:http://jsfiddle.net/cgSj3/

【讨论】:

  • 这是一个有趣的解决方案,但不能考虑预先存在的值,或占用超过 1 行的粘贴值。
  • var rows = textarea.rows
【解决方案3】:

对于那些对Rob W's 解决方案的 jQuery 版本感兴趣的人:

var textarea = jQuery('.textarea');
textarea.on("input", function () {
    jQuery(this).css("height", ""); //reset the height
    jQuery(this).css("height", Math.min(jQuery(this).prop('scrollHeight'), 200) + "px");
});

【讨论】:

  • 作者声明的解决方案喊不要使用 jquery。
  • 作者表示他们不想使用 jQuery 插件。这个解决方案不是插件,它是普通的 jQuery。
【解决方案4】:

...如果您需要一个无限扩展的文本区域(就像我一样),只需这样做:

var textarea = document.getElementById("textarea");

textarea.oninput = function() {
  textarea.style.height = ""; /* Reset the height*/
  textarea.style.height = textarea.scrollHeight + "px";
};

【讨论】:

    【解决方案5】:

    与公认的答案不同,我的函数关心padding-{top,bottom}border-{top,bottom}-width。它有很多参数。注意它没有设置window.addEventListener('resize')

    功能:

    // @author Arzet Ro, 2021 <arzeth0@gmail.com>
    // @license CC0 (Creative Commons Zero v1.0 Universal) (i.e. Public Domain)
    // @source https://stackoverflow.com/a/70341077/332012
    // Useful for elements with overflow-y: scroll and <textarea>
    // Tested only on <textarea> in desktop Firefox 95 and desktop Chromium 96.
    export function autoResizeScrollableElement (
        el: HTMLElement,
        {
            canShrink = true,
            minHeightPx = 0,
            maxHeightPx,
            minLines,
            maxLines,
        }: {
            canShrink?: boolean,
            minHeightPx?: number,
            maxHeightPx?: number,
            minLines?: number,
            maxLines?: number,
        } = {}
    ): void
    {
        const FN_NAME = 'autoResizeScrollableElement'
        if (
            typeof minLines !== 'undefined'
            && minLines !== null
            && Number.isNaN(+minLines)
        )
        {
            console.warn(
                '%O(el=%O):: minLines (%O) as a number is NaN',
                FN_NAME, el, minLines
            )
        }
        if (
            typeof maxLines !== 'undefined'
            && maxLines !== null
            && Number.isNaN(+maxLines)
        )
        {
            console.warn(
                '%O(el=%O):: maxLines (%O) as a number is NaN',
                FN_NAME, el, maxLines
            )
        }
        canShrink = (
            canShrink === true
            ||
            // @ts-ignore
            canShrink === 1 || canShrink === void 0 || canShrink === null
        )
    
        const style = window.getComputedStyle(el)
        const unpreparedLineHeight = style.getPropertyValue('line-height')
        if (unpreparedLineHeight === 'normal')
        {
            console.error('%O(el=%O):: line-height is unset', FN_NAME, el)
        }
        const lineHeightPx: number = (
            unpreparedLineHeight === 'normal'
            ? 1.15 * parseFloat(style.getPropertyValue('font-size')) // 1.15 is a wrong number
            : parseFloat(unpreparedLineHeight)
        )
    
        // @ts-ignore
        minHeightPx = parseFloat(minHeightPx || 0) || 0
        //minHeight = Math.max(lineHeightPx, parseFloat(style.getPropertyValue('min-height')))
        // @ts-ignore
        maxHeightPx = parseFloat(maxHeightPx || 0) || Infinity
        minLines = (
            minLines
            ? (
                Math.round(+minLines || 0) > 1
                ? Math.round(+minLines || 0)
                : 1
            )
            : 1
        )
        maxLines = (
            maxLines
            ? (Math.round(+maxLines || 0) || Infinity)
            : Infinity
        )
        //console.log('%O:: old ov.x=%O ov.y=%O, ov=%O', FN_NAME, style.getPropertyValue('overflow-x'), style.getPropertyValue('overflow-y'), style.getPropertyValue('overflow'))
        /*if (overflowY !== 'scroll' && overflowY === 'hidden')
        {
            console.warn('%O:: setting overflow-y to scroll', FN_NAME)
        }*/
        if (minLines > maxLines)
        {
            console.warn(
                '%O(el=%O):: minLines (%O) > maxLines (%O), '
                + 'therefore both parameters are ignored',
                FN_NAME, el, minLines, maxLines
            )
            minLines = 1
            maxLines = Infinity
        }
        if (minHeightPx > maxHeightPx)
        {
            console.warn(
                '%O(el=%O):: minHeightPx (%O) > maxHeightPx (%O), '
                + 'therefore both parameters are ignored',
                FN_NAME, el, minHeightPx, maxHeightPx
            )
            minHeightPx = 0
            maxHeightPx = Infinity
        }
        const topBottomBorderWidths: number = (
            parseFloat(style.getPropertyValue('border-top-width'))
            + parseFloat(style.getPropertyValue('border-bottom-width'))
        )
        let verticalPaddings: number = 0
        if (style.getPropertyValue('box-sizing') === 'border-box')
        {
            verticalPaddings += (
                parseFloat(style.getPropertyValue('padding-top'))
                + parseFloat(style.getPropertyValue('padding-bottom'))
                + topBottomBorderWidths
            )
        }
        else
        {
            console.warn(
                '%O(el=%O):: has `box-sizing: content-box`'
                + ' which is untested; you should set it to border-box. Continuing anyway.',
                FN_NAME, el
            )
        }
        const oldHeightPx = parseFloat(style.height)
        if (el.tagName === 'TEXTAREA')
        {
            el.setAttribute('rows', '1')
            //el.style.overflowY = 'hidden'
        }
        // @ts-ignore
        const oldScrollbarWidth: string|void = el.style.scrollbarWidth
        el.style.height = ''
    
        // Even when there is nothing to scroll,
        // it causes an extra height at the bottom in the content area (tried Firefox 95).
        // scrollbar-width is present only on Firefox 64+,
        // other browsers use ::-webkit-scrollbar
        // @ts-ignore
        el.style.scrollbarWidth = 'none'
    
        const maxHeightForMinLines = lineHeightPx * minLines + verticalPaddings // can be float
        // .scrollHeight is always an integer unfortunately
        const scrollHeight = el.scrollHeight + topBottomBorderWidths
        /*console.log(
            '%O:: lineHeightPx=%O * minLines=%O + verticalPaddings=%O, el.scrollHeight=%O, scrollHeight=%O',
            FN_NAME, lineHeightPx, minLines, verticalPaddings,
            el.scrollHeight, scrollHeight
        )*/
        const newHeightPx = Math.max(
            canShrink === true ? minHeightPx : oldHeightPx,
            Math.min(
                maxHeightPx,
                Math.max(
                    maxHeightForMinLines,
                    Math.min(
                        Math.max(scrollHeight, maxHeightForMinLines)
                        - Math.min(scrollHeight, maxHeightForMinLines) < 1
                        ? maxHeightForMinLines
                        : scrollHeight,
                        (
                            maxLines > 0 && maxLines !== Infinity
                            ? lineHeightPx * maxLines + verticalPaddings
                            : Infinity
                        )
                    )
                )
            )
        )
        // @ts-ignore
        el.style.scrollbarWidth = oldScrollbarWidth
        if (!Number.isFinite(newHeightPx) || newHeightPx < 0)
        {
            console.error(
                '%O(el=%O):: BUG:: Invalid return value: `%O`',
                FN_NAME, el, newHeightPx
            )
            return
        }
        el.style.height = newHeightPx + 'px'
        //console.log('%O:: height: %O → %O', FN_NAME, oldHeightPx, newHeightPx)
        /*if (el.tagName === 'TEXTAREA' && el.scrollHeight > newHeightPx)
        {
            el.style.overflowY = 'scroll'
        }*/
    }
    

    与 React (TypeScript) 一起使用:

    <textarea
        onKeyDown={(e) => {
            if (!(e.key === 'Enter' && !e.shiftKey)) return true
            e.preventDefault()
            // send the message, then this.scrollToTheBottom()
            return false
        }}
        onChange={(e) => {
            if (this.state.isSending)
            {
                e.preventDefault()
                return false
            }
            this.setState({
                pendingMessage: e.currentTarget.value
            }, () => {
                const el = this.chatSendMsgRef.current!
                engine.autoResizeScrollableElement(el, {maxLines: 5})
            })
            return true
        }}
    />
    

    对于 React onChange 就像 HTML5 中的 oninput,所以如果你不使用 React,那么使用 input 事件。


    其中一个答案使用rows 属性(而不是CSS 的height,就像我上面的代码那样),这是一个不使用外部变量的替代实现(但就像那个答案一样存在一个错误:因为@ 987654331@ 暂时设置为 1,当您输入时 &lt;html&gt; 的 scrollTop 会发生不好的事情,并且 &lt;html&gt; 可以滚动):

    // @author Arzet Ro, 2021 <arzeth0@gmail.com>
    // @license CC0 (Creative Commons Zero v1.0 Universal) (i.e. Public Domain)
    // @source https://stackoverflow.com/a/70341077/332012
    function autoResizeTextareaByChangingRows (
        el,
        {minLines, maxLines}
    )
    {
        const FN_NAME = 'autoResizeTextareaByChangingRows'
        if (
            typeof minLines !== 'undefined'
            && minLines !== null
            && Number.isNaN(+minLines)
        )
        {
            console.warn('%O:: minLines (%O) as a number is NaN', FN_NAME, minLines)
        }
        if (
            typeof maxLines !== 'undefined'
            && maxLines !== null
            && Number.isNaN(+maxLines)
        )
        {
            console.warn('%O:: maxLines (%O) as a number is NaN', FN_NAME, maxLines)
        }
        minLines = (
            minLines
            ? (
                Math.round(+minLines || 0) > 1
                ? Math.round(+minLines || 0)
                : 1
            )
            : 1
        )
        maxLines = (
            maxLines
            ? (Math.round(+maxLines || 0) || Infinity)
            : Infinity
        )
        el.setAttribute(
            'rows',
            '1',
        )
        const style = window.getComputedStyle(el)
        const unpreparedLineHeight = style.getPropertyValue('line-height')
        if (unpreparedLineHeight === 'normal')
        {
            console.error('%O:: line-height is unset for %O', FN_NAME, el)
        }
        const rows = Math.max(minLines, Math.min(maxLines,
            Math.round(
                (
                    el.scrollHeight
                    - parseFloat(style.getPropertyValue('padding-top'))
                    - parseFloat(style.getPropertyValue('padding-bottom'))
                ) / (
                    unpreparedLineHeight === 'normal'
                    ? 1.15 * parseFloat(style.getPropertyValue('font-size')) // 1.15 is a wrong number
                    : parseFloat(unpreparedLineHeight)
                )
            )
       ))
        el.setAttribute(
            'rows',
            rows.toString()
        )
    }
    
    const textarea = document.querySelector('textarea')
    textarea.oninput = function ()
    {
        autoResizeTextareaByChangingRows(textarea, {maxLines: 5})
    }
    

    【讨论】:

      【解决方案6】:

      使用

      &lt;div contentEditable&gt;&lt;/div&gt;

      也可以做同样的工作,自己扩展,不需要js

      【讨论】:

      • 这对我完全没有任何作用。
      猜你喜欢
      • 2020-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-02-15
      • 1970-01-01
      相关资源
      最近更新 更多