一個搜索頁面,上面輸入框,下面列表展現搜索到的結果。css
重點是:產品要求搜索框默認顯示一行,當輸入的文字超過一行時,輸入框的高度會隨着改變,直到輸入完畢。html
本想利用textarea實現,但textarea不支持自適應高度,而是定好高度或者是行數以後,超出部分就會顯示滾動條。只能另想。vue
根據需求,首先想到了張鑫旭僞類匹配列表數目實現微信羣頭像CSS佈局的技巧一文提到的文字多字號自動變小的技巧,但仔細一琢磨,不行。這個是根據內容元素的個數,進行處理,而這兒是輸入框,沒有內容元素。api
後面想到能夠利用html屬性contenteditable="true"
,加在div上讓其可編輯來模擬自適應高度。但是須要在vue中雙向綁定實現,這個不是很好處理。瀏覽器
後面想到利用textarea的row屬性,根據輸入內容的長度控制row的值,爲1-n行,但這個彷佛不是很智能,由於多少個字體一行不必定,英文、中文、數字的寬度不一致,並且row屬性在每一個瀏覽器中的表現不一致。微信
最後利用textarea,監聽change事件,讓其高度=其滾動條高度,來達到高度自適應。app
沒想到最後仍是利用了textarea。wordpress
參考:textarea如何實現高度自適應?函數
util.js佈局
/** * 文本框根據輸入內容自適應高度 * @param {HTMLElement} 輸入框元素 * @param {Number} 設置光標與輸入框保持的距離(默認0) * @param {Number} 設置最大高度(可選) * @callback {Function} 設置回調函數(可選) */ export const autoTextarea = function (elem, extra, maxHeight, callback) { extra = extra || 0; var isFirefox = !!document.getBoxObjectFor || 'mozInnerScreenX' in window, isOpera = !!window.opera && !!window.opera.toString().indexOf('Opera'), addEvent = function (type, callback) { elem.addEventListener ? elem.addEventListener(type, callback, false) : elem.attachEvent('on' + type, callback); }, getStyle = elem.currentStyle ? function (name) { var val = elem.currentStyle[name]; if (name === 'height' && val.search(/px/i) !== 1) { var rect = elem.getBoundingClientRect(); return rect.bottom - rect.top - parseFloat(getStyle('paddingTop')) - parseFloat(getStyle('paddingBottom')) + 'px'; }; return val; } : function (name) { return getComputedStyle(elem, null)[name]; }, minHeight = parseFloat(getStyle('height')); elem.style.resize = 'none'; var change = function () { var scrollTop, height, padding = 0, style = elem.style; if (elem._length === elem.value.length) return; elem._length = elem.value.length; if (!isFirefox && !isOpera) { padding = parseInt(getStyle('paddingTop')) + parseInt(getStyle('paddingBottom')); }; scrollTop = document.body.scrollTop || document.documentElement.scrollTop; elem.style.height = minHeight + 'px'; if (elem.scrollHeight > minHeight) { if (maxHeight && elem.scrollHeight > maxHeight) { height = maxHeight - padding; style.overflowY = 'auto'; } else { height = elem.scrollHeight - padding; style.overflowY = 'hidden'; }; style.height = height + extra + 'px'; scrollTop += parseInt(style.height) - elem.currHeight; document.body.scrollTop = scrollTop; document.documentElement.scrollTop = scrollTop; elem.currHeight = parseInt(style.height); callback(parseInt(style.height)); }; }; addEvent('propertychange', change); addEvent('input', change); addEvent('focus', change); change(); }; export const debounce = function (func, delay) { let timer; return function (...args) { if (timer) { clearTimeout(timer); } timer = setTimeout(() => { func.apply(this, args); }, delay || 500); } }
說明:因爲下面是列表,須要計算高度,爲了不從新再去獲取高度,因此加了個回調方法,把高度回調出去。
vue-search.vue
<template> <div class="search-list"> <div> <textarea ref="textarea" v-model="keywords" :maxlength="keywordsMax" @change="searchChange"></textarea> <span class="clear" @click="clearKeywords">x</span> </div> <div class="list" ref="list"> <ul> <li v-for="item in list" :key="item.id"> <span class="icon"></span> <dl> <dt>{{item.title}}</dt> <dd>{{item.desc}}</dd> </dl> </li> </ul> </div> </div> </template> <script> import { debounce, autoTextarea } from '@/util.js'; let rootFontSize = parseFloat(document.documentElement.style.fontSize); export default { data () { return { keywordsMax: 128, keywords: '', list: [] } }, mounted () { this.$nextTick(() => { let textarea = this.$refs.textarea; textarea.focus(); let prevHeight = 65; textarea && autoTextarea(textarea, 5, 0, (height) => { height += 20; if (height !== prevHeight) { prevHeight = height; let rem = height / rootFontSize; this.$refs.list.style.height = `calc(100% - ${rem}rem)`; } }); }) }, methods: { clearKeywords () { this.keywords = ''; this.list = []; let textarea = this.$refs.textarea; let height = 40; let rem = height / rootFontSize; textarea.style.height = `${rem}rem`; rem = (height + 20) / rootFontSize; this.$refs.list.style.height = `calc(100% - ${rem}rem)`; textarea.focus(); }, searchChange: debounce(function () { let trim = this.keywords.trim(); if (!trim) { this.list = []; return; } const params = { keywords: this.keywords } // 調api ... }) } } </script>
<style> .textarea{ width: 400px; min-height: 20px; max-height: 300px; _height: 120px; margin-left: auto; margin-right: auto; padding: 3px; outline: 0; border: 1px solid #a0b3d6; font-size: 12px; line-height: 24px; padding: 2px; word-wrap: break-word; overflow-x: hidden; overflow-y: auto; border-color: rgba(82, 168, 236, 0.8); box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.1), 0 0 8px rgba(82, 168, 236, 0.6); } </style> <div class="textarea" contenteditable="true"></div>