文章目录

今天观看学习Element的源码,看到textarea有一个自适应高度的属性,毕竟以前也接触过这方面的问题,你可以查看此处:更强大的textarea高度自适应来了解我之前写的一篇同样是实现textarea自适应高度,所以就好奇看一下它是怎么实现的。

先来看一下它的源码吧(各个阶段大致的做的事情我已经标到代码上了):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
let hiddenTextarea;

const HIDDEN_STYLE = `
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important
`;

// 所有可能会影响到height的css属性
const CONTEXT_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing'
];

// 获取设置在当前textarea上的css属性
function calculateNodeStyling(targetElement) {
const style = window.getComputedStyle(targetElement);

const boxSizing = style.getPropertyValue('box-sizing');

const paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
);

const borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
);

const contextStyle = CONTEXT_STYLE
.map(name => `${name}:${style.getPropertyValue(name)}`)
.join(';');

return { contextStyle, paddingSize, borderSize, boxSizing };
}

export default function calcTextareaHeight(
targetElement,
minRows = 1,
maxRows = null
) {
// 如果不存在就新建一个隐藏的textarea
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}

let {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(targetElement);

// 将获取到得当前得textarea的css属性作用于隐藏的textarea
hiddenTextarea.setAttribute('style', `${contextStyle};${HIDDEN_STYLE}`);
// 将当前的textarea的value设置到隐藏的textarea上面
hiddenTextarea.value = targetElement.value || targetElement.placeholder || '';

// 获取隐藏的textarea的height
let height = hiddenTextarea.scrollHeight;
const result = {};

if (boxSizing === 'border-box') {
height = height + borderSize;
} else if (boxSizing === 'content-box') {
height = height - paddingSize;
}

hiddenTextarea.value = '';
let singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;

// 如果设置有最小行数和最大行数时的判断条件
if (minRows !== null) {
let minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
result.minHeight = `${ minHeight }px`;
}
if (maxRows !== null) {
let maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
height = Math.min(maxHeight, height);
}
// 将得到的height的高度设置到当前的textarea上面
result.height = `${ height }px`;
// 删除掉无用的隐藏的textarea
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
return result;
};

大致思路就是将当前textarea的所有可能会影响到height的css属性全部设置给一个隐藏的textarea上,并且两个的value一样,再将隐藏的textarea的高度设置给当前的textarea,如果设置有最小和最大行数,则再做相应的事件。

如果您不太清楚getComputedStyle和getPropertyValue,可以直接查看此处http://www.zhangxinxu.com/wordpress/2012/05/getcomputedstyle-js-getpropertyvalue-currentstyle/

不过这个是Element的一个文件,我们一般情况下也没办法使用,所以我就想办法把它修改为了一个依赖与jQuery的js文件,使用的时候只需要直接引用这个js文件就可以了,其他事件都不需要做,并且也支持设置最小和最大行数的。

下面放上一个例子:
html:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>textarea自适应高度</title>
<style>
textarea {
display: block;
resize: vertical;
padding: 5px 15px;
line-height: 1.5;
box-sizing: border-box;
width: 100%;
font-size: 14px;
color: #5a5e66;
background-color: #fff;
background-image: none;
border: 1px solid #d8dce5;
border-radius: 4px;
transition: border-color .2s cubic-bezier(.645,.045,.355,1);
margin-bottom: 20px;
}
</style>
</head>
<body>
<textarea name="text" cols="30" rows="1"></textarea>
<textarea name="text" cols="30" rows="2"></textarea>
<textarea name="text" cols="30" data-min-rows="2" data-max-rows="4"></textarea>
<script src="http://apps.bdimg.com/libs/jquery/1.11.3/jquery.js"></script>
<script src="autoheight-textarea.js"></script>
</body>
</html>

autoheight-textarea.js文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
$(function() {
var hiddenTextarea;

var HIDDEN_STYLE = `
height:0 !important;
visibility:hidden !important;
overflow:hidden !important;
position:absolute !important;
z-index:-1000 !important;
top:0 !important;
right:0 !important;
`;

// 所有可能会影响到height的css属性
var CONTEXT_STYLE = [
'letter-spacing',
'line-height',
'padding-top',
'padding-bottom',
'font-family',
'font-weight',
'font-size',
'text-rendering',
'text-transform',
'width',
'text-indent',
'padding-left',
'padding-right',
'border-width',
'box-sizing'
];
// 获取设置在当前textarea上的css属性
function calculateNodeStyling(targetElement) {
var style = window.getComputedStyle(targetElement);

var boxSizing = style.getPropertyValue('box-sizing');

var paddingSize = (
parseFloat(style.getPropertyValue('padding-bottom')) +
parseFloat(style.getPropertyValue('padding-top'))
);

var borderSize = (
parseFloat(style.getPropertyValue('border-bottom-width')) +
parseFloat(style.getPropertyValue('border-top-width'))
);

var contextStyle = CONTEXT_STYLE
.map(function(value){
return value + ':' + style.getPropertyValue(value)
}).join(';');

return { contextStyle, paddingSize, borderSize, boxSizing };
}
$('body').on('focus', 'textarea', function () {
// 如果不存在就新建一个隐藏的textarea
var _this = $(this);
if (!hiddenTextarea) {
hiddenTextarea = document.createElement('textarea');
document.body.appendChild(hiddenTextarea);
}
var {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(_this[0]);
// 将获取到得当前得textarea的css属性作用于隐藏的textarea
hiddenTextarea.setAttribute('style', HIDDEN_STYLE + contextStyle);
}).on('keydown keyup', 'textarea', function(){
var _this = $(this);
var {
paddingSize,
borderSize,
boxSizing,
contextStyle
} = calculateNodeStyling(_this[0]);
// 将获取到得当前得textarea的css属性作用于隐藏的textarea
hiddenTextarea.setAttribute('style', HIDDEN_STYLE + contextStyle);
// 将当前的textarea的value设置到隐藏的textarea上面
hiddenTextarea.value = _this[0].value || _this[0].placeholder || '';

// 获取隐藏的textarea的height
var height = hiddenTextarea.scrollHeight;

if (boxSizing === 'border-box') {
height = height + borderSize;
} else if (boxSizing === 'content-box') {
height = height - paddingSize;
}

hiddenTextarea.value = '';
var singleRowHeight = hiddenTextarea.scrollHeight - paddingSize;

// 如果设置有最小行数和最大行数时的判断条件,如果没有设置则取rows为最小行数
var minRows = _this.attr('rows') ? _this.attr('rows') : _this.attr('data-min-rows') ? _this.attr('data-min-rows') : 1;
var maxRows = _this.attr('data-max-rows') ? _this.attr('data-max-rows') : null;
if (minRows !== null) {
var minHeight = singleRowHeight * minRows;
if (boxSizing === 'border-box') {
minHeight = minHeight + paddingSize + borderSize;
}
height = Math.max(minHeight, height);
}
if (maxRows !== null) {
var maxHeight = singleRowHeight * maxRows;
if (boxSizing === 'border-box') {
maxHeight = maxHeight + paddingSize + borderSize;
}
height = Math.min(maxHeight, height);
}

// 将得到的height的高度设置到当前的textarea上面
_this.css('height', height + 'px');
}).on('blur', 'textarea', function () {
// 删除掉无用的隐藏的textarea
hiddenTextarea.parentNode && hiddenTextarea.parentNode.removeChild(hiddenTextarea);
hiddenTextarea = null;
})
})

Element的源码还是写的很赞的,有时间多看看各种框架的源码。

文章目录