与公认的答案不同,我的函数关心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,当您输入时 <html> 的 scrollTop 会发生不好的事情,并且 <html> 可以滚动):
// @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})
}