家好,不知道你們是否和我一樣存在這樣的困惑呢,雖然css入門(mén)容易,但是其內(nèi)容太多,好多屬性看了似是而非,覺(jué)得自己看懂了,到自己用的時(shí)候又很犯難了,看到漂亮的效果還是無(wú)從下手,這主要還是自己對(duì)新屬性實(shí)踐太少了,不能進(jìn)行靈活應(yīng)用,CSS總讓一些人找不到感覺(jué)。其實(shí)學(xué)好CSS真的沒(méi)有太多捷徑,和JS編程一樣,要重視對(duì)待,要多看和多練,因?yàn)楝F(xiàn)在的CSS不再是以前的CSS啦。
比如這兩本書(shū)《 CSS 權(quán)威指南第四版》,1000多頁(yè),買(mǎi)了好幾個(gè)月我到現(xiàn)在還沒(méi)看完,文字實(shí)在太枯燥了,看完了忘,忘了繼續(xù)看,實(shí)在看不下去,不知道大家有沒(méi)有同樣的感受呢?
好了,廢話(huà)不多說(shuō),今天我們要做的一個(gè)案例就是做一個(gè)常見(jiàn)的例子:在不少網(wǎng)站右側(cè)都有一個(gè)固定浮動(dòng)的留言圖標(biāo),我們點(diǎn)擊這個(gè)圖標(biāo),就會(huì)側(cè)滑顯示留言?xún)?nèi)容面板。你也許會(huì)說(shuō)這個(gè)不簡(jiǎn)單嗎,使用JQ就能輕松實(shí)現(xiàn),但是我想說(shuō)的,為了網(wǎng)站的性能,能用CSS實(shí)現(xiàn)的盡量不要用JS,因?yàn)楝F(xiàn)在CSS已足夠強(qiáng)大。
今天這個(gè)例子,我們將使用純CSS實(shí)現(xiàn)這個(gè)效果,這里我們將用到” CSS checkbox hack“的技術(shù),效果如下圖所示:
基于上面的效果圖,我們要?jiǎng)?chuàng)建三個(gè)元素,一個(gè) checkbox 元素以及對(duì)應(yīng)的 label 標(biāo)記,和一個(gè)表單面板元素。
這里用到了一個(gè) CSS 特性值得大家關(guān)注下:<label> 標(biāo)簽的 for 屬性用于指定與哪個(gè)表單元素進(jìn)行關(guān)聯(lián),擴(kuò)大表單元素的點(diǎn)擊區(qū)域,我們點(diǎn)擊 label 元素標(biāo)記,其對(duì)應(yīng)的表單元素將會(huì)被聚焦選中。
這個(gè)特性是我們實(shí)現(xiàn)這個(gè)案例的技巧之一,再結(jié)合 CSS checkbox 的偽類(lèi)選擇器進(jìn)行留言面板的顯示與隱藏,這樣我們就可以擺脫 JS 來(lái)實(shí)現(xiàn)這個(gè)案例。
基于以上思路 ,我們開(kāi)始動(dòng)手吧,首先我們先放置 checkbox,和其對(duì)應(yīng)的 label,最后添加表單面板和相關(guān)的表單元素。
我們將通過(guò)表單的 id 屬性與表單中l(wèi)abel元素的 for 值與其關(guān)聯(lián),最終我們完成了 HTML 結(jié)構(gòu)如下段代碼所示:
<input type="checkbox" id="mycheckbox">
<label for="mycheckbox" class="feedback-label">FEEDBACK</label>
<form class="form">
<div>
<label for="fullname">Full Name</label>
<input type="text" id="fullname">
</div>
<div>
<label for="email">Email</label>
<input type="email" id="email">
</div>
<div>
<label for="comment">Comment</label>
<textarea id="comment"></textarea>
</div>
<div>
<button type="submit">Send</button>
</div>
</form>
完成后的效果圖如下:
現(xiàn)在我們開(kāi)始添加一些基礎(chǔ)的CSS的式,這里我們用到了CSS自定義變量,方便我們?nèi)中薷模€有一些 reset 規(guī)則,和表單的基礎(chǔ)規(guī)則樣式,示例代碼如下:
:root {
--white: white;
--gradient: linear-gradient(-45deg, #FFA600 0%, #FF5E03 50%);
--form: #eeefef;
--border-radius: 4px;
--form-width: 500px;
--form-mob-width: 320px;
}
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
body {
font: 20px/1.5 sans-serif;
background: var(--white);
}
h1 {
font-size: 2rem;
text-align: center;
margin-top: 20vh;
}
button,
label {
cursor: pointer;
}
label {
display: block;
}
button,
input,
textarea {
font-family: inherit;
font-size: 100%;
border: none;
}
textarea {
resize: none;
}
1、由于 checkbox 這個(gè)元素在案例中無(wú)需顯示,我們只是用其的偽類(lèi)特性結(jié)合 label 控制留言面板的顯示與隱藏,因此我們需要將其移出可視區(qū)域,記住這里不能用隱藏屬性(display:none)。示例代碼如下:
[type="checkbox"] {
position: absolute;
left: -9999px;
}
2、接下來(lái),我們需要添加這些CSS操作:
對(duì)應(yīng)的CSS代碼如下:
/*CUSTOM VARIABLES HERE*/
.feedback-label,
.form {
position: fixed;
top: 50%;
right: 0;
}
.feedback-label {
transform-origin: top right;
transform: rotate(-90deg) translate(50%, -100%);
z-index: 2;
}
.form {
width: var(--form-width);
max-height: 90vh;
transform: translate(100%, -50%);
padding: 30px;
overflow: auto;
background: var(--form);
z-index: 1;
}
小提示:
1、這里需要強(qiáng)調(diào)的是 feedback-label 樣式,在其垂直變換時(shí),我們先逆時(shí)針進(jìn)行了旋轉(zhuǎn),其 x ,y 軸的方向也是隨著旋轉(zhuǎn)的,所以是translate(50%, -100%),將其垂直居中。
2、在 form 樣式里,我們使用 transform 方法,translate(100%, -50%) 將其移出頁(yè)面顯示區(qū)域
3、我們繼續(xù),大家不要著急,馬上就快完成了,我們需要將頁(yè)面弄的漂亮些,添加一些樣式,示例代碼如下:
/*CUSTOM VARIABLES HERE*/
.feedback-label,
.form input,
.form textarea,
.form button {
border-radius: var(--border-radius);
}
.feedback-label,
.form button {
background: var(--gradient);
color: var(--white);
}
.feedback-label:hover,
.form button:hover {
filter: hue-rotate(-45deg);
}
.feedback-label {
padding: 5px 10px;
border-radius: var(--border-radius) var(--border-radius) 0 0;
}
form div:not(:last-child) {
margin-bottom: 20px;
}
form div:last-child {
text-align: right;
}
.form input,
.form textarea {
padding: 0 5px;
width: 100%;
}
.form button {
padding: 10px 20px;
width: 50%;
max-width: 120px;
}
.form input {
height: 40px;
}
.form textarea {
height: 220px;
}
小提示:這里我們的背景色用到了 linear-gradient() 線(xiàn)性漸變,實(shí)現(xiàn)了一個(gè)漂亮的顏色漸變背景。還有一個(gè) CSS3 語(yǔ)法需要關(guān)注下:hue-rotate,色調(diào)旋轉(zhuǎn)濾鏡,方便我們改變當(dāng)前的顏色。
我們通過(guò)點(diǎn)擊 checkbox 對(duì)應(yīng)的 label 標(biāo)簽進(jìn)行切換和顯示留言面板,這里我們用到了 :checked 偽類(lèi),以及 ~(后續(xù)同胞選擇器)和 +(緊鄰?fù)x擇器),輔助元素的選擇進(jìn)行樣式變換,示例代碼如下:
[type="checkbox"]:checked + .feedback-label {
transform: rotate(-90deg) translate(50%, calc((var(--form-width) + 100%) * -1));
}
[type="checkbox"]:focus + .feedback-label {
outline: 2px solid rgb(77, 144, 254);
}
[type="checkbox"]:checked ~ .form {
transform: translate(0, -50%);
}
.feedback-label,
.form {
transition: all 0.35s ease-in-out;
}
這里有幾個(gè)樣式規(guī)則我們需要聊一下:
最后,特別重點(diǎn)提示下我們做頁(yè)面要考慮頁(yè)面響應(yīng)式適配的問(wèn)題,這里我們需要針對(duì)手機(jī)設(shè)備做一些樣式的調(diào)整,比如更改表單面板的寬度由原來(lái)的 500px 調(diào)整到 320px,以及初始字體的大小,調(diào)整成16px。
最終添加的響應(yīng)式代碼如下:
/*CUSTOM VARIABLES HERE*/
@media screen and (max-width: 600px) {
body {
font-size: 16px;
}
.form {
padding: 15px;
width: var(--form-mob-width);
}
form div:not(:last-child) {
margin-bottom: 10px;
}
[type="checkbox"]:checked + .feedback-label {
transform: rotate(-90deg) translate(50%, calc((var(--form-mob-width) + 100%) * -1));
}
}
好了,到這里,我們的案例就全部完成了,你可以欣賞下自己完成的杰作啦,實(shí)現(xiàn)起來(lái)是不是很簡(jiǎn)單呢,最后我還是建議大家還是親自動(dòng)手實(shí)踐一遍,這樣才能加深對(duì)本案例用到的CSS屬性的理解。
最終完成的效果,大家可以點(diǎn)擊以下網(wǎng)址進(jìn)行在線(xiàn)體驗(yàn):
https://www.qianduandaren.com/demo/feedback/
今天的內(nèi)容就和大家分享到這里,感謝你的閱讀,如果你喜歡我的分享,麻煩給個(gè)關(guān)注、點(diǎn)贊加轉(zhuǎn)發(fā)哦,你的支持,就是我分享的動(dòng)力,后續(xù)會(huì)持續(xù)分享 CSS 常用案例和技巧,歡迎持續(xù)關(guān)注。
基礎(chǔ)章節(jié):這30個(gè)CSS選擇器,你必須熟記(上)
基礎(chǔ)章節(jié):這30個(gè)CSS選擇器,你必須熟記(中)
基礎(chǔ)章節(jié):這30個(gè)CSS選擇器,你必須熟記(下)
使用 CSS Checkbox Hack 技術(shù)制作一個(gè)手風(fēng)琴組件
擊右上方紅色按鈕關(guān)注“web秀”,讓你真正秀起來(lái)
隨著前端開(kāi)發(fā)越來(lái)越關(guān)注效率:通過(guò)選擇器的使用和簡(jiǎn)化代碼來(lái)快速加載渲染。像Less、SCSS這樣的預(yù)處理器在工作的時(shí)候,需要繞的路較長(zhǎng),而直接使用css速度會(huì)更快。這里涵蓋了20個(gè)css技巧來(lái)幫助你減少重復(fù)規(guī)則和復(fù)寫(xiě),在布局中標(biāo)準(zhǔn)化樣式流程,不僅可以幫助你高效地創(chuàng)建自己的框架,而且可以解決許多常見(jiàn)的問(wèn)題。
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
如果您對(duì)CSS比較陌生,看看這篇文章CSS選擇器如此之多,你了解多少?
css重置庫(kù)如normalize.css已經(jīng)被使用很多年了,它們可以為你的網(wǎng)站樣式提供一個(gè)比較清晰的標(biāo)準(zhǔn),來(lái)確保跨瀏覽器之間的一致性。大多數(shù)項(xiàng)目并不需要這些庫(kù)包含的所有規(guī)則,可以通過(guò)一條簡(jiǎn)單的規(guī)則來(lái)應(yīng)用于布局中的所有元素,刪除所有的margin、padding改變?yōu)g覽器默認(rèn)的盒模型。
*{ box-sizing:border-box; margin:0; padding:0 }
使用box-sizing聲明是可選擇,如果你使用下面繼承的盒模型形式可以跳過(guò)它
讓盒模型從html 繼承:
html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; }
當(dāng)你多少次試著去設(shè)計(jì)柵格布局如:組合或者圖片畫(huà)廊,如果使用浮動(dòng)的方式,那么就需要去清除浮動(dòng)和重置外邊距來(lái)使其分解成所需要行數(shù)。為了避免nth-、first-、last-child 問(wèn)題 ,可以使用flexbox 的space-between 屬性值
.flex-container{ display:flex; justify-content:space-between; } .flex-container .item{ flex-basis:23%; }
在web設(shè)計(jì)中,我們通常使用:last-child nth-child 選擇器來(lái)覆蓋原先聲明應(yīng)在父選擇器上的樣式。比如說(shuō)一個(gè)導(dǎo)航菜單,通過(guò)使用borders 來(lái)給每個(gè)鏈接Link創(chuàng)建分割符,然后再在加上一條規(guī)則 解除最后一個(gè)link的border
.nav li { border-right: 1px solid #666; } .nav li:last-child { border-right: none; }
這是一種很混亂的方式,它不僅強(qiáng)制瀏覽器以一種方式渲染,然后又通過(guò)特定的選擇器來(lái)撤銷(xiāo)它。這樣覆蓋樣式是不可避免的。然而,最重要的是,我們可以通過(guò)使用:not偽類(lèi)(pseudo-class) 在你想聲明的元素上僅僅只使用一種樣式:
.nav li:not(:last-child) { border-right: 1px solid #666; }
上面就是,除了最后一個(gè)li以外,所有的 .nav li 都加上了border樣式,是不是很簡(jiǎn)單! 當(dāng)然,你也可以使用 .nav li+li或者 .nav li:first-child ~li ,但是 :not是更有語(yǔ)義化(semantic)和容易理解的。
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
導(dǎo)致低樣式效率(inefficient stylesheets)的一件事就是不斷的重復(fù)聲明。最好是做下項(xiàng)目規(guī)劃和組合規(guī)則,這樣CSS會(huì)更流暢。實(shí)現(xiàn)這一點(diǎn),就需要我們理解級(jí)聯(lián)(cascade),以及如何在通用選擇器寫(xiě)的樣式可以繼承在其他地方。行間距(line-height)可以作為 給你的整個(gè)項(xiàng)目設(shè)置的一個(gè)屬性,不僅可以減小代碼量,而且可以讓你的網(wǎng)站的樣式給一個(gè)標(biāo)準(zhǔn)的外觀
body { line-height: 1.5; }
請(qǐng)注意,這里的聲明沒(méi)有單位,我們只是告訴瀏覽器 讓它渲染行高是 渲染字體大小的1.5倍
在沒(méi)有準(zhǔn)備使用CSSGrid 布局的時(shí)候,設(shè)置垂直居中布局的全局規(guī)則是一個(gè)很好的方式,可以為優(yōu)雅(elegantly)的設(shè)置內(nèi)容布局奠定一個(gè)基礎(chǔ)
html, body { height: 100%; margin: 0; } body { -webkit-align-items: center; -ms-flex-align: center; align-items: center; display: -webkit-flex; display: flex; }
這15種CSS居中的方式,你都用過(guò)哪幾種?
SVG使用于所有分辨類(lèi),并且所有瀏覽器也都支持。所以可以將.png .jpg .gif 等文件 丟棄。FontAwsome5中 也提供了SVG的圖標(biāo)字體。設(shè)置SVG的格式就跟其他圖片類(lèi)型一樣:
.logo { background: url("logo.svg"); }
溫馨提示:如果將SVG用在可交互的元素上比如說(shuō)button,SVG 會(huì)產(chǎn)生無(wú)法加載的問(wèn)題。可以通過(guò)下面這個(gè)規(guī)則來(lái)確保SVG可以訪問(wèn)到(確保在HTML中已設(shè)置適當(dāng)?shù)腶ria屬性)
.no-svg .icon-only:after { content: attr(aria-label); }
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
使用通用選擇器(universal selector)* 和相鄰的兄弟選擇器(adjacent sibling selector)+ 可以提供一個(gè)強(qiáng)大的的CSS功能,給緊跟其他元素中的文檔流中的所有元素設(shè)置統(tǒng)一的規(guī)則
* + * { margin-top: 1.5rem; }
這是一個(gè)很棒的技巧,可以幫你創(chuàng)建更加均勻的類(lèi)型跟間距。在上面的列子中,跟在其他元素后面的元素,比如說(shuō)H3后面的H4,或者一個(gè)段落之后的一個(gè)段落,他們之間至少1.5rems的間距(大約為30px)
一致的垂直節(jié)奏提供了一種視覺(jué)美學(xué),使內(nèi)容更具可讀性。如果owl選擇器過(guò)于通用,請(qǐng)?jiān)谠貎?nèi)使用通用選擇器(*)為布局的特定部分創(chuàng)建一致的垂直節(jié)奏:
.intro > * { margin-bottom: 1.25rem; }
假設(shè)您希望對(duì)換行到多行的長(zhǎng)文本行應(yīng)用統(tǒng)一的間距、邊距、突出顯示或背景色,但不希望整個(gè)段落或標(biāo)題看起來(lái)像一個(gè)大塊。Box Decoration Break屬性允許您僅對(duì)文本應(yīng)用樣式,同時(shí)保持填充和頁(yè)邊距的完整性。如果要在懸停時(shí)應(yīng)用突出顯示,或在滑塊中設(shè)置子文本樣式以具有突出顯示的外觀,則此功能尤其有用:
.p { display: inline-block; box-decoration-break: clone; -o-box-decoration-break: clone; -webkit-box-decoration-break: clone; }
內(nèi)聯(lián)塊聲明允許將顏色、背景、頁(yè)邊距和填充應(yīng)用于每行文本,而不是整個(gè)元素,克隆聲明確保將這些樣式均勻地應(yīng)用于每行。
表格可能很難處理,所以嘗試使用table-layout:fixed來(lái)保持單元格相等寬度:
.calendar { table-layout: fixed; }
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
這對(duì)于通過(guò)CMS插入的鏈接特別有用,CMS通常不具有類(lèi)屬性,并幫助您在不影響級(jí)聯(lián)的情況下對(duì)其進(jìn)行特定樣式設(shè)置。例如,<a>元素沒(méi)有文本值,但href屬性有一個(gè)鏈接:
a[href^="http"]:empty::before { content: attr(href); }
說(shuō)到鏈接樣式,您可以在幾乎每個(gè)樣式表中找到一個(gè)通用的A樣式。這迫使您為子元素中的任何鏈接編寫(xiě)額外的覆蓋和樣式規(guī)則,并且在使用像WordPress這樣的CMS時(shí),可能會(huì)導(dǎo)致您的主鏈接樣式比按鈕文本顏色更容易出現(xiàn)問(wèn)題。嘗試這種較少干擾的方式為“默認(rèn)”鏈接添加樣式:
a[href]:not([class]) { color: #999; text-decoration: none; transition: all ease-in-out .3s; }
要?jiǎng)?chuàng)建具有固有比率的框,您需要做的就是將頂部或底部填充應(yīng)用于div:
.container { height: 0; padding-bottom: 20%; position: relative; } .container div { border: 2px dashed #ddd; height: 100%; left: 0; position: absolute; top: 0; width: 100%; }
使用20%進(jìn)行填充使得框的高度等于其寬度的20%。無(wú)論視口的寬度如何,子div都將保持其縱橫比(100%/ 20%= 5:1)。
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
這個(gè)技巧不是關(guān)于代碼縮減,而是關(guān)于細(xì)化設(shè)計(jì)細(xì)節(jié)的。破碎的圖像發(fā)生的原因有很多,要么不雅觀,要么導(dǎo)致混亂(只是一個(gè)空元素)。用這個(gè)小小的CSS創(chuàng)建更美觀的效果:
img { display: block; font-family: Helvetica, Arial, sans-serif; font-weight: 300; height: auto; line-height: 2; position: relative; text-align: center; width: 100%; } img:before { content: "We're sorry, the image below is missing :("; display: block; margin-bottom: 10px; } img:after { content: "(url: " attr(src) ")"; display: block; font-size: 12px; }
在設(shè)置根目錄的基本字體大小后,例如html字體大小:15px;,可以將包含元素的字體大小設(shè)置為rem:
article { font-size: 1.25rem; } aside { font-size: .9rem; }
然后將文本元素的字體大小設(shè)置為em
h2 { font-size: 2em; } p { font-size: 1em; }
現(xiàn)在,每個(gè)包含的元素都變得分區(qū)化,更易于樣式化、更易于維護(hù)和靈活。
web開(kāi)發(fā)中該用 em 還是 rem 呢?
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
當(dāng)您處理無(wú)法從源代碼輕松控制的內(nèi)容時(shí),這對(duì)于自定義用戶(hù)樣式表來(lái)說(shuō)是一個(gè)很好的技巧。這個(gè)技巧將幫助您避免在加載頁(yè)面時(shí)自動(dòng)播放視頻中的聲音干擾訪問(wèn)者,并再次提供了精彩的:not()偽選擇器:
video[autoplay]:not([muted]) { display: none; }
響應(yīng)布局中的字體大小應(yīng)該能夠自動(dòng)調(diào)整到視區(qū),從而保存編寫(xiě)媒體查詢(xún)的工作,以處理字體大小。可以使用:not和視區(qū)單位,根據(jù)視區(qū)高度和寬度計(jì)算字體大小:
:root { font-size: calc(1vw + 1vh + .5vmin); }
現(xiàn)在,您可以使用根em單位,該單位基于:not:
body { font: 1rem/1.6 sans-serif; }
結(jié)合上面的rem/em技巧以獲得更好的控制。有關(guān)管理Safari舊版本的提示,請(qǐng)參閱CSS Fix for iOS VH Unit Bug。
為了避免移動(dòng)瀏覽器(iOS Safari等)在點(diǎn)擊<select>下拉列表時(shí)放大HTML表單元素,請(qǐng)?jiān)谔砑觙ont-size樣式:
input[type="text"], input[type="number"], select, textarea { font-size: 16px; }
最后,最強(qiáng)大的CSS級(jí)別來(lái)自于CSS變量,它允許您聲明一組公共屬性值,這些值可以通過(guò)樣式表中任何位置的關(guān)鍵字重用。你可能有一套顏色在整個(gè)項(xiàng)目中使用,以保持一致性。在CSS中反復(fù)重復(fù)這些顏色值不僅是件煩人的事情,而且還容易出錯(cuò)。如果某個(gè)顏色在某個(gè)時(shí)刻需要改變,你就不得不去尋找和替換,這是不可靠或不快速的,當(dāng)為最終用戶(hù)構(gòu)建產(chǎn)品時(shí),變量使得定制變得容易得多。例如:
如何提升你的CSS技能?掌握這20個(gè)css技巧即可[完整版]
:root { --main-color: #06c; --accent-color: #999; } h1, h2, h3 { color: var(--main-color); } a[href]:not([class]), p, footer span{ color: var(--accent-color); }
喜歡小編的點(diǎn)擊關(guān)注,了解更多知識(shí)!
何保持頁(yè)面樣式基本不變的前提下將HTML頁(yè)面導(dǎo)出為PDF,下面提供一些示例代碼,純屬個(gè)人原創(chuàng),如對(duì)你有幫助請(qǐng)記得加關(guān)注、加收藏、點(diǎn)贊、轉(zhuǎn)發(fā)、分享~謝謝~~
<div>
<!-- 要打印的內(nèi)容區(qū) -->
<div ref="contentRef">
<div class="print-item print-out-flow">這是脫離文檔流的內(nèi)容區(qū)域</div>
<div class="print-item">這是一行內(nèi)容,也是最小葉子元素內(nèi)容</div>
</div>
<!-- 打印內(nèi)容容器 -->
<div ref="printContainerRef" class="print-container"></div>
</div>
/**
* 1.使用一個(gè)隱藏div裝載有滾動(dòng)條的div.innerHTML
* 2.隱藏div使用position: absolute, z-index: -999, left: -9999px, width: 900px 控制讓用戶(hù)無(wú)感知
* 3.根據(jù)需要覆寫(xiě)隱藏div內(nèi)html樣式(例如textarea多行顯示有問(wèn)題, 可以新增一個(gè)隱藏的div
* 包裹textarea的綁定值, 然后在打印樣式中覆寫(xiě)樣式, 隱藏textarea并顯示對(duì)應(yīng)div)
*/
handleExport() {
// 下面是VUE組件內(nèi)獲取DOM元素代碼,將內(nèi)容放置到打印區(qū)(定義的隱藏DIV)中
const contentRef = this.$refs.contentRef as HTMLElement;
const printContainerRef = this.$refs.printContainerRef as HTMLElement;
// 打印區(qū)的需額外處理絕對(duì)定位值, 調(diào)整使得第一個(gè)元素的.top值為0, 以便于頁(yè)面計(jì)算
printContainerRef.innerHTML = contentRef.innerHTML;
// 所有葉子div元素加上 print-item 樣式名, 脫離文檔流的額外添加 print-out-flow
handlePrintItem(printContainerRef); // 解決多頁(yè)內(nèi)容可能被切割問(wèn)題
html2canvas(printContainerRef, {allowTaint: false, useCORS: true}).then((canvas: any) => {
const contentHeight = canvas.height;
const contentWidth = canvas.width;
// pdf每頁(yè)顯示的內(nèi)容高度
const pageHeight = contentWidth / 595.28 * 841.89;
// 未生成pdf的頁(yè)面高度
let offsetHeight = contentHeight;
// 頁(yè)面偏移值
let position = 0;
// a4紙的尺寸[595.28, 841.89], canvas圖片按a4紙大小縮放后的寬高
const imgWidth = 595.28;
const imgHeight = 595.28 / contentWidth * contentHeight;
const dataURL = canvas.toDataURL('image/jpeg', 1.0);
const doc = new jsPDF('p', 'pt', 'a4');
if (offsetHeight < pageHeight) {
doc.addImage(dataURL, 'JPEG', 0, 0, imgWidth, imgHeight);
} else {
while (offsetHeight > 0) {
doc.addImage(dataURL, 'JPEG', 0, position, imgWidth, imgHeight);
offsetHeight -= pageHeight;
position -= 841.89;
if (offsetHeight > 0) {
doc.addPage();
}
}
}
doc.save(this.generateReportFileName());
printContainerRef.innerHTML = '';
});
}
上干貨代碼:上面分頁(yè)導(dǎo)出PDF可能網(wǎng)上能看到類(lèi)型代碼,但絕對(duì)找不到下面的代碼,純手搓解決分頁(yè)元素被切開(kāi)問(wèn)題(思路:獲取自身定位,如自己剛好在被分頁(yè)處,則加上一定的margin-top值將內(nèi)容向下移)
/**
* 處理打印元素項(xiàng), 修復(fù)分頁(yè)后被切割的元素
* @param printContainerRef 打印內(nèi)容div容器
* @param itemClassName 打印最小元素標(biāo)識(shí)類(lèi)名
* @param outFlowClassName 脫離文檔流的元素標(biāo)識(shí)類(lèi)名
*/
export function handlePrintItem(
printContainerRef: HTMLElement,
itemClassName: string = 'print-item',
outFlowClassName: string = 'print-out-flow'
): void {
const rootClientRect = printContainerRef.getBoundingClientRect();
// 初始化頁(yè)面相關(guān)數(shù)據(jù)
const totalHeight = rootClientRect.height; // 內(nèi)容總高度
const a4PageHeight = (printContainerRef.clientWidth / 595.28) * 841.89; // a4紙高度
let pageNum = Math.ceil(totalHeight / a4PageHeight); // 總頁(yè)數(shù)
let addPageHeight = 0; // 修正被分割元素而增加的頁(yè)面高度總和
let currentPage = 1; // 當(dāng)前正在處理切割的頁(yè)面
const splitItemObj: { [key: number]: HTMLElement[] } = {}; // 內(nèi)容中各頁(yè)被切割元素存儲(chǔ)對(duì)象
const printItemNodes: NodeListOf<HTMLElement> = printContainerRef.querySelectorAll(`.${itemClassName}`);
for (let item of printItemNodes) {
// 如果當(dāng)前頁(yè)已經(jīng)是最后一頁(yè), 則中斷判斷
if (currentPage >= pageNum) {
break;
}
// 獲取元素絕對(duì)定位數(shù)據(jù)
const clientRect = item.getBoundingClientRect();
let top = clientRect.top;
const selfHeight = clientRect.height;
// 如果當(dāng)前元素距離頂部高度大于當(dāng)前頁(yè)面頁(yè)腳高度, 則開(kāi)始判斷下一頁(yè)頁(yè)腳被切割元素
if (top > currentPage * a4PageHeight) {
// 換頁(yè)前修正上一頁(yè)被切割元素
addPageHeight += fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
pageNum = Math.ceil((totalHeight + addPageHeight) / a4PageHeight);
top = item.getBoundingClientRect().top;
currentPage++;
}
// 如果元素剛好處于兩頁(yè)之間, 則記錄該元素
if (top > (currentPage - 1) * a4PageHeight && top < currentPage * a4PageHeight && top + selfHeight > currentPage * a4PageHeight) {
if (!splitItemObj[currentPage]) {
splitItemObj[currentPage] = [];
}
splitItemObj[currentPage].unshift(item);
// 如果當(dāng)前元素是最后一個(gè)元素, 則直接處理切割元素, 否則交由處理下一頁(yè)元素時(shí)再處理切割
if (item === printItemNodes[printItemNodes.length - 1]) {
fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
}
}
}
}
/**
* 修復(fù)當(dāng)前頁(yè)所有被切割元素
* @param currentPage 當(dāng)前頁(yè)
* @param pageHeight 每頁(yè)高度
* @param splitElementItems 當(dāng)前被切割元素?cái)?shù)組
* @param outFlowClassName 脫離文檔流的樣式類(lèi)名
*/
function fixSplitItems(
currentPage: number,
pageHeight: number,
splitElementItems: HTMLElement[],
outFlowClassName: string
): number {
if (!splitElementItems || !splitElementItems.length) {
return 0;
}
const yMargin = 5; // y方向距離頁(yè)眉的距離
const splitItemsMinTop = getSplitItemsMinTop(splitElementItems);
if (!splitItemsMinTop) {
return 0;
}
let fixHeight = currentPage * pageHeight - splitItemsMinTop + yMargin;
const outFlowElement = splitElementItems.find((item) => item.classList.contains(outFlowClassName));
if (outFlowElement && outFlowElement.parentElement) {
const parentPreviousElement = outFlowElement.parentElement.previousElementSibling as HTMLElement;
fixHeight += getMarinTopNum(parentPreviousElement, outFlowElement.parentElement);
outFlowElement.parentElement.style.marginTop = `${fixHeight}px`;
return fixHeight;
}
splitElementItems.forEach((splitElement) => {
splitElement.style.marginTop = `${fixHeight}px`;
});
return fixHeight;
}
/**
* 獲取被切割元素?cái)?shù)組中最小高度值(如一行有多個(gè)元素被切割,則選出距離頂部最小的高度值)
* @param splitElementItems 當(dāng)前被切割元素?cái)?shù)組
*/
function getSplitItemsMinTop(
splitElementItems: HTMLElement[]
): number | undefined {
// 獲取元素中最小top值作為基準(zhǔn)進(jìn)行修正
let minTop: number | undefined;
let minElement: HTMLElement | undefined;
splitElementItems.forEach((splitElement) => {
let top = splitElement.getBoundingClientRect().top;
if (minTop) {
minTop = top < minTop ? top : minTop;
minElement = top < minTop ? splitElement : minElement;
} else {
minTop = top;
minElement = splitElement;
}
});
// 修正當(dāng)前節(jié)點(diǎn)及其前面同層級(jí)節(jié)點(diǎn)的margin值
if (minTop && minElement) {
const previousElement = splitElementItems[splitElementItems.length - 1].previousElementSibling as HTMLElement;
minTop -= getMarinTopNum(previousElement, minElement);
}
return minTop;
}
/**
* 通過(guò)前一個(gè)兄弟元素和元素自身的位置確認(rèn)一個(gè)距離頂部高度修正值
* @param previousElement 前一個(gè)兄弟元素
* @param curElement 當(dāng)前元素
*/
function getMarinTopNum(previousElement: HTMLElement, curElement: HTMLElement): number {
let preMarginNum = 0;
let curMarginNum = 0;
if (previousElement) {
// 獲取外聯(lián)樣式需要getComputedStyle(), 直接.style時(shí)對(duì)象的值都為空
const previousMarginBottom = window.getComputedStyle(previousElement).marginBottom;
preMarginNum = previousMarginBottom ? Number(previousMarginBottom.replace('px', '')) : 0;
}
const marginTop = window.getComputedStyle(curElement).marginTop;
curMarginNum = marginTop ? Number(marginTop.replace('px', '')) : 0;
return preMarginNum > curMarginNum ? preMarginNum : curMarginNum;
}
以上純?cè)瓌?chuàng)!歡迎加關(guān)注、加收藏、點(diǎn)贊、轉(zhuǎn)發(fā)、分享(代碼閑聊站)~
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。