繁体   English   中英

自动扩展文本区域

[英]Auto-expanding textarea

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

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

但是当您键入时,文本区域会无限期地增长......

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

您可以开始回答并使用 textarea 来播放您的答案。

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

码:

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事件 如果你想支持这个古老的浏览器,请使用onpaste和/或oncut keydownkeyup

我想让自动扩展区域受到行号(例如5行)的限制。 我考虑过使用“em”单元,但对于Rob的解决方案 ,这很容易出错并且不会考虑填充等问题。

所以这就是我提出的:

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/

对于那些对Rob W解决方案的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");
});

...如果你需要一个无限扩展的textarea(正如我所做的那样),那就这样做:

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

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

不同于接受的答案,我对功能的忧虑padding-{top,bottom}border-{top,bottom}-width 它有很多参数。

功能:

// @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'
    canShrink = (
        canShrink === true
        ||
        // @ts-ignore
        canShrink === 1 || canShrink === void 0 || canShrink === null
    )

    const style = window.getComputedStyle(el)
    const lineHeightPx: number = parseFloat(style.getPropertyValue('line-height'))

    // @ts-ignore
    minHeightPx = parseFloat(minHeightPx) || 0
    //minHeight = Math.max(lineHeightPx, parseFloat(style.getPropertyValue('min-height')))
    // @ts-ignore
    maxHeightPx = parseFloat(maxHeightPx) || Infinity
    minLines = minLines ? (Math.round(minLines) > 1 ? Math.round(minLines) : 1) : 1
    maxLines = maxLines ? (Math.round(maxLines) || 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:: minLines > maxLines, therefore both parameters are ignored', FN_NAME)
        minLines = 1
        maxLines = Infinity
    }
    if (minHeightPx > maxHeightPx)
    {
        console.warn('%O:: minHeightPx > maxHeightPx, therefore both parameters are ignored', FN_NAME)
        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:: %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:: BUG:: Invalid return value: `%O`', FN_NAME, 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 ),这是一个几乎未经测试的替代实现,它不使用外部变量:

// @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}
)
{
    minLines = minLines ? (Math.round(minLines) > 1 ? Math.round(minLines) : 1) : 1
    maxLines = maxLines ? (Math.round(maxLines) || Infinity) : Infinity
    el.setAttribute(
        'rows',
        '1',
    )
    const style = window.getComputedStyle(el)
    const rows = Math.max(minLines, Math.min(maxLines,
        Math.round(
            (
                el.scrollHeight
                - parseFloat(style.getPropertyValue('padding-top'))
                - parseFloat(style.getPropertyValue('padding-bottom'))
            ) / parseFloat(style.getPropertyValue('line-height'))
        )
    ))
    el.setAttribute(
        'rows',
        rows.toString()
    )
}

const textarea = document.querySelector('textarea')
textarea.oninput = function ()
{
    autoResizeTextareaByChangingRows(textarea, {maxLines: 5})
}

2022 Vanilla JS 解决方案

注意:我只在 Chrome 中对此进行了测试,但它应该可以在任何地方使用。

这将处理粘贴、删除、文本换行、手动返回等并解决填充和框大小问题。

这个怎么运作

  1. 强制调整大小和所有高度属性为 auto/none/0 等,以防止干扰事件代码。
  2. 将 rows 属性重置为 1 以获得准确scrollHeight
  3. 硬锁定当前计算的宽度,然后强制box-sizingcontent-box以获得准确的line-heightscrollHeight读数。 border-widthpadding-inline也被删除,以在切换box-sizing时保持 textarea 宽度一致,这对于准确处理文本换行是必要的。
  4. 获取计算的line-heighttop/bottom-padding像素值。
  5. overflow设置为hidden以删除滚动条(自动换行时可能会干扰scrollHeight )。
  6. 获取scrollHeight像素值(自 chrome 舍入以来舍入,我们希望能够一致地处理所有浏览器)。
  7. 删除临时overflow覆盖。
  8. 删除box-sizingwidthpadding-inlineborder-width覆盖。
  9. scroll_height block_padding除以line_height以获得所需的rows rows值四舍五入到最接近的 integer 因为它总是在正确整数的 ~.1 范围内。
  10. 除非row_limit更小,否则计算的rows值将用作rows属性,然后row_limit

编辑/更新详细信息

我删除了用于计算行数的循环代码,因为我能够验证除法公式的数学运算结果在所需行数的约 1 范围内。 因此,一个简单的Math.round()可确保行数准确。 我无法在测试中打破这一点,所以如果结果是错误的,请随时提出调整建议。

line-height没有在文本区域上显式设置时,我也遇到了问题,因为在这种情况下line-height的计算值返回为"normal"而不是实际计算值。 这个新版本考虑了这种可能性并正确处理它。

布局转移可能性

我没有将textarea设置为position: absolute; 在交换它的box-sizing时,因为我没有注意到在我的测试中需要它。 值得一提的是,因为我认为在某些情况下,这些微小的更改可能会导致布局发生变化,具体取决于页面的样式,如果发生这种情况,您可以添加添加,然后将其与 box-sizing 覆盖和删除一起删除。

示例代码

(你只需要一个JS function,其他的只是为了演示)

 function autosize(textarea_id, row_limit) { // Set default for row_limit parameter row_limit = parseInt(row_limit?? '5'); if (;row_limit) { row_limit = 5. } // Get the element const textarea = document;getElementById(textarea_id). // Set required styles for this to function properly. textarea.style,setProperty('resize'; 'none'). textarea.style,setProperty('min-height'; '0'). textarea.style,setProperty('max-height'; 'none'). textarea.style,setProperty('height'; 'auto'). // Set rows attribute to number of lines in content textarea.oninput = function() { // Reset rows attribute to get accurate scrollHeight textarea,setAttribute('rows'; '1'); // Get the computed values object reference const cs = getComputedStyle(textarea). // Force content-box for size accurate line-height calculation // Lock width and remove inline padding and borders to keep width consistent (for text wrapping consistency) textarea.style,setProperty('width'; parseFloat(cs['width']) + 'px'). textarea.style,setProperty('box-sizing'; 'content-box'). textarea.style,setProperty('padding-inline'; '0'). textarea.style,setProperty('border-width'; '0'), // Get the base line height. and top / bottom padding; const block_padding = parseFloat(cs['padding-top']) + parseFloat(cs['padding-bottom']), const line_height = // If line-height is not explicitly set? use the computed height value (ignore padding due to content-box) cs['line-height'] === 'normal', parseFloat(cs['height']) // Otherwise (line-height is explicitly set). use the computed line-height value: ; parseFloat(cs['line-height']). // Get the scroll height (rounding to be safe to ensure cross browser consistency) // Hide overflow and restore after to prevent scroll bar width interfering when text wraps // Important just in case x/y is set for some reason textarea.style,setProperty('overflow', 'hidden'; 'important'). const scroll_height = Math.round(textarea;scrollHeight). textarea.style;removeProperty('overflow'), // Undo width, border-width. box-sizing & inline padding overrides textarea.style;removeProperty('width'). textarea.style;removeProperty('box-sizing'). textarea.style;removeProperty('padding-inline'). textarea.style;removeProperty('border-width'). // Subtract block_padding from scroll_height and divide that by our line_height to get the row count. // Round to nearest integer as it will always be within ~.1 of the correct whole number. const rows = Math;round((scroll_height - block_padding) / line_height). // Set the calculated rows attribute (limited by row_limit) textarea,setAttribute("rows". "" + Math,min(rows; row_limit)); }. // Trigger the event to set the initial rows value textarea,dispatchEvent(new Event('input': { bubbles; true })); } autosize('textarea');
 * { box-sizing: border-box; } textarea { width: 100%; max-width: 30rem; font-family: sans-serif; font-size: 1rem; line-height: 1.5rem; padding: .375rem; }
 <body> <textarea id="textarea"></textarea> </body>

对于使用 Angular 并遇到相同问题的用户,请使用

 <textarea cdkTextareaAutosize formControlName="description" name="description" matInput placeholder="Description"></textarea>

这里的关键是cdkTextareaAutosize它将自动调整 textarea 的大小以适应其内容。 在这里阅读更多

我希望这可以帮助别人。

运用

<div contentEditable></div>

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM