021年你需要知道的HTML標簽和屬性
Web開發人員都在廣泛的使用HTML。無論你使用什么框架或者選擇哪個后端語言,框架在變,但是HTML始終如一。盡管被廣泛使用,但還是有一些標簽或者屬性是大部分開發者不熟知的。雖然現在有很多的模版引擎供我們使用,但是樂字節教育的老師和我們說還是需要盡可能的熟練掌握HTML內容,就像CSS一樣。
在我看來,最好盡可能使用HTML特性來實現我們的功能,而不是使用JavaScript實現相同的功能,盡管我承認編寫HTML可能會是重復的和無聊的。
盡管許多開發人員每天都在使用HTML,但他們并沒有嘗試改進自己的項目,也沒有真正利用HTML的一些鮮為人知的特性。
下面這5個通過HTML標簽/屬性實現的功能我覺得需要了解一下:
圖片懶加載
圖片懶加載可以幫助提升網站的性能和響應能力。圖片懶加載可以避免立即加載那些不在屏幕中立即顯示的圖片素材,當用戶滾動臨近圖片時再去開始加載。
換言之,當用戶滾動到圖片出現時再進行加載,否則不加載。這就降低了屏幕內容展示過程中的圖片素材的請求數量,提升了站點性能。
往往我們都是通過javascript來實現的,通過監聽頁面滾動事件來確定加載對應的資源。但是,在不完全考慮兼容性的場景下,我們其實可以直接通過HTML來直接實現。
注:本篇的提到的標簽和屬性的兼容性需要大家根據實際場景來選取是否使用
可以通過為圖片文件添加loading="lazy"的屬性來實現:
輸入提示
當用戶在進行輸入搜索功能時,如果能夠給出有效的提示,這會大大提升用戶體驗。輸入建議和自動完成功能現在到處可見,我們可以使用Javascript添加輸入建議,方法是在輸入框上設置事件偵聽器,然后將搜索到的關鍵詞與預定義的建議相匹配。
其實,HTML也是能夠讓我們來實現預定義輸入建議功能的,通過<datalist>標簽來實現。需要注意的是,使用時這個標簽的id屬性需要和input元素的list屬性一致。
Picture標簽
你是否遇到過在不同場景或者不同尺寸的設備上面的時候,圖片展示適配問題呢?我想大家都遇到過。
針對只有一個尺寸的圖片素材的時候,我們往往可以通過CSS的object-fit屬性來進行裁切適配。但是有些時候需要針對不同的分辨率來顯示不同尺寸的圖片的場景的時候,我們是否可以直接通過HTML來實現呢?
HTML提供了<picture>標簽,允許我們來添加多張圖片資源,并且根據不同的分辨率需求來展示不同的圖片。
我們可以定義不同區間的最小分辨率來確定圖片素材,這個標簽的使用有些類似<audio>和<video>標簽。
Base URL
當我們的頁面有大量的錨點跳轉或者靜態資源加載時,并且這些跳轉或者資源都在統一的域名的場景時,我們可以通過<base>標簽來簡化這個處理。
例如,我們有一個列表需要跳轉到微博的不同大V的主頁,我們就可以通過設置來簡化跳轉路徑
<base>標記必須具有href和target屬性。
頁面重定向(刷新)
當我們希望實現一段時間后或者是立即重定向到另一個頁面的功能時,我們可以直接通過HTML來實現。
我們經常會遇到有些站點會有這樣一個功能,“5s后頁面將跳轉”。這個交互可以嵌入到HTML中,直接通過<meta>標簽,設置http-equiv="refresh"來實現
這里content屬性指定了重定向發生的秒數。值得一提的是,盡管谷歌聲稱這種形式的重定向和其他的重定向方式一樣可用,但是使用這種類型的重定向其實并不是那么的優雅,往往會顯得很突兀。
因此,最好在某些特殊的情況下使用它,比如在長時間用戶不活動之后再重定向到目標頁面。
后記
HTML和CSS是非常強大的,哪怕我們僅僅使用這兩種技術也能創建出一些奇妙的網站。雖然它們的使用量很大很普遍,還是有很多的開發者并沒有真正的深入了解他們,還有很多的內容需要我們深入的去學習和理解,實踐,有很多的技巧等待著我們去發現。
文章轉載至樂字節
最后給大家推薦幾個b站超詳細的Java自學課:
Servlet入門教程BV1D5411373E
Vue、Vuejs教程,BV19V41177od
SpringBoot+Vue項目實戰BV1o64y117qQ
iframe元素
創建包含另外一個文檔的內聯框架(即行內框架)。
CSS3規范,視口單位主要包括以下4個:
1、vw : 1vw 等于視口寬度的1%;
2、vh : 1vh 等于視口高度的1%;
3、vmin : 選取vw和vh中最小的那個;
4、vmax : 選取vw和vh中最大的那個;
100%高度和寬度:
body {
margin: 0; /* Reset default margin */
}
iframe {
display: block; /* iframes are inline by default */
background: #fff;
border: none; /* Reset default border */
height: 100vh; /* Viewport-relative units */
width: 100vw;
}
HTML中嵌入iframe
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<title>XXXX調查表</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<style type="text/css">
body html{
margin:0;
padding:0;
}
#app{
margin: 0 auto;
width: 600px;
}
iframe {
display: block;
background: #FFF;
border: none;
width:100vw; /* Viewport-relative units */
height:100vh;
width:100%;
}
</style>
</head>
<body>
<div id="app">
<div>
<button type="button"><span>返回</span></button>
<button type="button" onclick="print()"><span>打印</span></button>
</div>
<h2>公司部門調查表</h2>
<iframe src="C:/Users/dd/Desktop/003.html" scrolling="no"></iframe>
</div>
</html>
被嵌入頁面:
<!doctype html>
<html lang="zh-CN">
<head>
<title>XXXX調查表</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<script src="https://cdn.staticfile.org/vue/2.2.2/vue.min.js"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<link href="https://cdn.bootcdn.net/ajax/libs/antd/4.18.2/antd.variable.css" rel="stylesheet">
<!--
https://v1-cn.vuejs.org/guide/forms.html
-->
</head>
<body>
<div id="app">
<p><h2>公司部門調查表</h2></p>
<form v-model="form" v-on:submit.prevent="saved()" >
<fieldset>
<legend>職員信息</legend>
<ol>
<li>
<label>姓名: </label>
<input v-model="form.name" type="text" name="name" autofocus/>
</li>
<li>
<label>年齡: </label>
<input v-model="form.age" type="number" name="age" />
</li>
<li>
<label>性別:</label>
<input v-model="form.sex" type="radio" name="sex" value="未知" />未知
<input v-model="form.sex" type="radio" name="sex" value="男" />男
<input v-model="form.sex" type="radio" name="sex" value="女" />女
</li>
<li>
<label>籍貫:</label>
<select v-model="form.nativePlace">
<optgroup label="山西省">
<option>太原市</option>
</optgroup>
<optgroup label="北京">
<option>北京市</option>
</optgroup>
</select>
</li>
<li>
<label>愛好:</label>
<input v-model='form.hobbys' type="checkbox" name="hobby" value="爬山" />爬山
<input v-model='form.hobbys' type="checkbox" name="hobby" value="涉水" />涉水
<input v-model='form.hobbys' type="checkbox" name="hobby" value="下棋" />下棋
<input v-model='form.hobbys' type="checkbox" name="hobby" value="游戲" />游戲
</li>
<li>
<label>所在部門:</label>
<select v-model="form.department">
<option v-for="(item, index) in form.departments" :key="item">{{item}}</option>
</select>
</li>
<li>
<label>職位:</label>
<input v-model="form.title" type="text" name="title" />
</li>
<li>
<label>提交建議:</label>
<input v-model="form.advise" type="file" name="form.advise" />
</li>
<li>
<label>填表日期:</label>
<input v-model="form.fillDate" type="date" name="fillDate">
</li>
</ol>
</fieldset>
<fieldset>
<legend>反饋意見</legend>
<ol>
<li>
<label>你對公司目前的發展表示:</label>
<input v-model="form.development" type="radio" name="development" value="滿意" /> 滿意
<input v-model="form.development" type="radio" name="development" value="不滿意" /> 不滿意
<input v-model="form.development" type="radio" name="development" value="一般" /> 一般
</li>
<li>
<label>你對公司的任職崗位表示:</label>
<input v-model="form.jobIdentification" type="radio" name="jobIdentification" value="認同" /> 認同
<input v-model="form.jobIdentification" type="radio" name="jobIdentification" value="不認同" /> 不認同
<input v-model="form.jobIdentification" type="radio" name="jobIdentification" value="一般" /> 一般
</li>
<li>
<label>你對公司的期望: </label> <br />
<textarea v-model="form.expect" rows="4" name="neme" cols="50"></textarea>
</li>
</ol>
</fieldset>
<input type="submit" value="提交">
<input type="reset" value="重置">
</form>
</div>
<script>
var now = new Date();
var year = now.getFullYear();
var month = now.getMonth()+1;
var day = now.getDay();
//
var app = new Vue({
el: '#app',
data() {
return {
form: {
name:"孔子",
age: 2000,
sex: "男",
nativePlace: "北京市",
hobbys: ["爬山","下棋"],
departments: ["財務部","法務部","加盟部","商務部","技術部"],
department: "技術部",
title: "工程師",
fillDate: year + "-" + month + "-" + day,
development: "一般",
jobIdentification: "不認同",
expect: "一切都美好~"
}
}
},
methods:{
saved(){
alert(JSON.stringify(this.form));
axios.post('http://192.168.1.116:8080/data/post',this.form,{
headers: { "token": "token123" }
}).then(res => {
console.log("result", JSON.stringify(res.data));
})
}
}
});
</script>
</html>
VUE中嵌入:
不管的后端還是前端表單的使用率是極高的,但風格和團隊之間個人的編寫風格各異,尤其的多條件,多規則,大量表單頁面,維護和保持統一編寫就變的比較困難,之前使用vue2版本的框架的時候基于之前低代碼開發的經驗編寫了一套 基于json描述生成的 表單組件,vue版本升級到vue3之后自然也需要一套與之相匹配的,簡單易用的表單組件。
凡事都有前置條件,因為業務的需要,后臺系統使用微前端的qiankun框架,但比較可惜的是vite尚不被qiankun支持,當然也有一些 相應的插件可以解決這個問題,但考慮到其非官方產品,企業從穩定性出發排除了vite
所以下面我講到的 組件是基于 vue3+typscript+webpack+elementulplus來進行二次封裝實現的。
我們要做的組件就是為了簡潔,可維護,可擴展 ,使用者不需要了解組件的內部實現,僅需要按照組件的配置要求進行配置即可實現功能,當然我們要盡可能的滿足需求,但覺不是100%滿足,其實一個優秀的組件能夠完成業務的80%的需求已經非常了不起了。在進行通用組件設計的時候切記不可大而全,如果一個組件使用的學習成本較高,基本也失去了封裝的意義。當然框架除外,比如vue
我的的目標是:
我們的設計結構是
module是我們組成表單的一個個的最小的自定義組件單元,。其實modle也可以不存在,直接在render函數里使用 elementUIplus里定義好的組件,這又是另一個話題了,這里我們使用自定義組件也是為了方便自定義和擴展。
data是一種json格式的數據結構,用來對模塊進行描述,(低代碼平臺的實現本質上也是如此。通過拖拽或其他方式對數據進行拼裝然后展示)
render 層是 vue3框架提供的 render函數,結合h()實現VirtualDom的的渲染,因為是h()是創建虛擬 DOM 節點 (vnode)的方法,因此不存在性能問題,在實現上和正常寫法沒有區別。
view 做為展示層 及我們展示的頁面。
elementuiplus 的form組件數據層面需要傳遞兩組數據(需要提交的數據和校驗規則)
數據和規則分離并不合理,所以在這里我們在json里把 需要校驗的數據和校驗規則放在一組里。
{
blockName: "Input",
attrsData: {
type: "text",
field: "name",
label: "姓名",
placeholder: "Please input name",
maxlength: 20,
minlength: 1,
showWordLimit: true,
clearable: true,
disabled: false,
inputStyle: { color: "red"
},
initValue:'666'
},
rule: [
{
required: true,
message: "Please input Activity name",
trigger: "blur",
},
{ min: 3, max: 5, message: "Length should be 3 to 5", trigger: "blur"
},
]
}
數據有三部分,即 blockName(組件名稱),attrsData(組件屬性的描述),rule(校驗規則)
<template>
<el-input
:placeholder="placeholder"
:maxlength="maxlength"
:minlength="minlength"
:clearable="clearable"
:require="require"
:name="name"
:formatter="formatter"
:show-password="showPassword"
:parser="parser"
:prefix-icon="prefixIcon"
:input-Style="inputStyle"
v-model="value"
/>
</template>
<script lang="ts">
import { useGetAttrsHook } from "../hooks/common.hook";
export default {
setup(props, context) {
const {
clearValue,
test,
value,
placeholder,
maxlength,
minlength,
clearable,
formatter,
showPassword,
parser,
require,
options,
name,
field,
clear,
prefixIcon,
activeText,
inactiveText,
} = useGetAttrsHook(props, context);
return {
clearValue,
test,
value,
};
},
};
</script>
以上面的代碼為例,在組件里我們使用elemnetUIplus提供的 表單組件。并把相應的 屬性進行了綁定,除了value其他屬性都只需要進行正常的 attrs 傳遞即可,value則需要實現數據的雙向綁定,并在我們點擊父組件的提交按鈕時傳遞給父組件進行提交,所以在這里就需要做一些特別的處理。來實現數據雙向傳遞,注意是雙向傳遞。如果不自定義組件也可以在 h函數渲染的時候直接使用 elementUIplus提供的組件,省去這一步。但雙向綁定需要想辦法實現,并且靈活性也差一些。不能在組件里定義一些自己的邏輯和樣式。
我們知道組件上使用v-model實現組件上的雙向綁定,如果是常規寫法自然沒有問題,但很遺憾我們的render 是不走尋常路的.先看下vue3的文檔的實現方式
https://cn.vuejs.org/guide/components/v-model.html
通過文檔我們知道 v-model 實際上就是一個語法糖,他的本來面目是這樣的
<CustomInput
:modelValue="searchText"
@update:modelValue="newValue=>searchText = newValue"
/>
對于我們來說這仍然是常規寫法,因為在h()里面這樣是行不通的。那么在h()中的寫法應該是這樣的
h(ASwitch, {
modelValue: this.value,
["onUpdate:modelValue"]: (data) => {
this.value = data;
},
});
如果監聽的是一個原生的input這個寫法是生效的,但可惜在我寫的的組件上并不好使。之所以在本文中寫出來是給大家一個參考或者各位有人發現我的錯誤之處,可以有實現。
那么還有什么方式實現數據的雙向傳遞呢,那就是 子組件調用父組件方法,父組件調用子組件方法,并互相進行數據傳遞了。
那么在vue3 中我們只能這樣去進行實現了,我們先談下子組件調用父組件的方法。
https://cn.vuejs.org/guide/components/events.html
<createForm
:data="fromData"
:ruleForm="ruleForm"
@setData="setData"
ref="childComp"
:reset="reset.type"
/>
父組件通過@綁定一個方法到子組件,子組件通過使用 defineEmits()宏來聲明它要觸發的事件,然后通過emit('方法名',入參)來實現父組件的調用。明白了這個概念就可以了,因為之后我們需要在h()上來實現這樣的一個功能。
還有就是我們需要另一個功能就是父組件傳遞一個指令給子組件,子組件監聽到命令后,執行自己的一個方法,用來清空自己的 value 也就是組件上傳遞的屬性 :reset="reset.type" 所有子組件如果監聽到父組件這個值的變化就清空value。
其實除了傳遞指令還有另一個方法就是父組件調用子組件的內的清除value的方法,但vue3父組件掉子組件的方法,因為涉及到ref的綁定 如果在h函數里實現比較麻煩,我沒有實現。有興趣的同學可以嘗試一下。
vue3 h函數的寫法與vue2 有很大不同,在學習 render函數的實現之前我們需要對 h函數的變化有所了解。
vue2 props 格式化語法
{
class: ['button', 'is-outlined'],
style: { color: '#34495E' },
attrs: { id: 'submit' },
domProps: { innerHTML: '' },
on: { click: submitForm },
key: 'submit-button'
}
vue3 props格式化語法
{
class: ['button', 'is-outlined'],
style: { color: '#34495E' },
//屬性不需要放在 attrs domProps on這些字段下了。
id: 'submit',
innerHTML: '',
onClick: submitForm,
key: 'submit-button'
}
我們可以看到vue3 渲染函數的屬性更加的扁平化,一些屬性不需要在包含在對應的字段里了。我個人覺的之前的更加嚴謹。僅個人觀點。
了解了渲染函數的變化我們來看下渲染方法的實現 ,先看代碼。
import {h,ref} from 'vue'
import CreateBlock from './createBlock';
import { ElFormItem} from 'element-plus';
const renderConfig =(h, data,func,reset)=> {
if (!data) { return console.error('no data'); }
return h(ElFormItem, { label: data.attrsData.label, prop: data.attrsData.field }, [h(CreateBlock, {
...data, clear: reset,['onSetData']:(data) => {
func(data)
} })])
}
export default {
props:{
data: {
type: Array,
default() {
return [];
},
},
reset: {
type: Number,
default() {
return 0
}
}
},
setup(props, context) {
// const list = props.data
const blockFunc = (data) => {
// console.log('傳入的data',data)
context.emit('setData', data)
}
return {
blockFunc
}
},
render(cxt) {
const list = cxt.data
const reset = cxt.reset
const childNodes = list.map((n) => renderConfig(h, n,cxt.blockFunc,reset));
const result = h('div',childNodes);
return result;
}
}
createFrom.ts 做了三件事,1,接受到了父組件傳遞過來的數據,2,根據傳過來的json遍歷渲染 子組件
3.把接受到的屬性,方法,和 組件名稱 傳遞給子組件
CreateBlock 會根據json的blackName返回相應的組件,所以我們渲染處理的組件不順序不會有問題
setup 函數里的context.emit('setData', data) 用來實現子組件向父組件傳遞數據
renderConfig 函數里的 clear: reset 屬性則是父組件向子組件傳遞清除命令的
整體邏輯如下;
1.render 接收到 json數據 然后循環調用 renderConfig 函數
2.renderConfig函數 給每個子組件外層包裹了 ElFormItem 并給ElFormItem提供了相應的屬性如 label,prop
3.renderConfig函數把json遍歷到的每條數據都通過 props 傳遞給了createBlock 同時給每個子組件綁定了setdata函數,注意這里要用onsetData
import { h,toRefs } from 'vue'
export default {
setup(props, context) {
const newMap: Array<object> = [];
try {
const elObj = require.context("../module", true, /\.vue$/);
elObj.keys().forEach((key) => {
const blockName = key.split("/")[1].split('.')[0];
const el = elObj(key).default;
if (!el) return;
newMap[blockName] = el;
});
} catch (error) {
console.log(error);
}
const block = newMap[context.attrs.blockName + 'Block'];
const attrsData = { ...context.attrs.attrsData };
const func = (data) => {
context.emit('SetData', data)
}
const getAttrs = ()=>{
return { ...context.attrs }
}
return {
block,
attrsData,
func,
getAttrs
}
},
render(cxt) {
const { clear } = cxt.getAttrs()
return h(cxt.block, { ...cxt.attrsData,clear,['onsetData']: cxt.func})
}
}
可以看到我們使用 require.context("../module", true, /\.vue$/); 來獲取 module文件夾下的所以組件,并按文件名稱轉換成數組和對應的引用地址,存放在newMap變量里。然后使用 context.attrs.blockName獲取 父組件傳遞過來的屬性 “blockName” 找到我們需要的子組件宣傳出來。是不是很簡單。
子組件的實現有很多通用方法 common.hook.ts
import {
toRefs,
toRef,
ref,
useAttrs,
reactive,
watch,
onMounted,
watchEffect,
} from "vue";
/* eslint-disable */
export const useGetAttrsHook = (props, context) => {
const {
placeholder,
maxlength,
minlength,
clearable,
formatter,
showPassword,
parser,
require,
options,
name,
field,
clear,
prefixIcon,
initValue,
activeText,
inactiveText,
} = context.attrs;
let value = ref(initValue);
console.log("value---555", options);
watchEffect(() => {
let data = {};
if (typeof field === "string") {
data[field] = value.value;
}
context.emit("setData", data);
});
watch(
() => props.clear,
(newValue, oldValue) => {
console.log("oldValue, newValue--", newValue, oldValue);
if (newValue == 1) {
clearValue();
}
}
);
const test = ref("ok");
const clearValue = () => {
value.value = "";
};
return {
clearValue,
test,
value,
placeholder,
maxlength,
minlength,
clearable,
formatter,
showPassword,
parser,
require,
options,
name,
field,
clear,
initValue,
prefixIcon,
activeText,
inactiveText
};
}
/* eslint-enable */
最后一起看下實現的效果
最后
目前暴露的數據和接口比較多,還做進一步的封裝和優化。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。