理解定位,首先要了解文檔流是什么。
文件流:指盒子按照html標簽編寫的順序,從上到下,從左到右排列,塊元素占一行,從左到右排列,每一行元素在一行中從左到右排列,先寫的先,后寫的排在后面,每個盒子都占據自己的位置。
位置可以使方框脫離文檔流,就好比將元素分割成幾層,沒有位置的在一層,定位在沒有定位的上面一層。
你可以用Z-index設置等級,Z-index越大,顯示的就越向前。在創建網頁彈框時,通常將其設置為最大。
我們可以使用css的position屬性來設置元素的定位類型,postion的設置項如下:
relative生成相對定位元素,元素所占據的文檔流的位置保留,元素本身相對自身原位置進行偏移。
absolute生成絕對定位元素,元素脫離文檔流,不占據文檔流的位置,可以理解為漂浮在文檔流的上方,相對于上一個設置了定位的父級元素來進行定位,如果找不到,則相對于body元素進行定位。
fixed生成固定定位元素,元素脫離文檔流,不占據文檔流的位置,可以理解為漂浮在文檔流的上方,相對于瀏覽器窗口進行定位。
static默認值,沒有定位,元素出現在正常的文檔流中,相當于取消定位屬性或者不設置定位屬性。
<!DOCTYPE html>
<html lang="en">
<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>元素的定位</title>
<style>
.con1,.con2,.con3,.con4,.con5{
border: 1px solid #000;
margin: 10px;
height: 400px;
}
/* 這個div屬于它下屬div的父元素,如果沒有設置浮動,當下屬div設置absolute時,相對于body偏移*/
.con3{
position: relative;
}
.no_pos_1,.no_pos_2{
height: 100px;
width: 400px;
margin :10px;
font-size: 16px;
}
.no_pos_1{
background-color: red;
}
.no_pos_2{
background-color: gold;
}
.relative_1,.relative_2{
height: 100px;
width: 400px;
margin-top :10px;
font-size: 16px;
}
.relative_1{
background-color: red;
position: relative;
left: 50px;
top: 50px;
}
.relative_2{
background-color: gold;
}
.absolute_1,.absolute_2{
height: 100px;
width: 400px;
margin-top :10px;
font-size: 16px;
}
.absolute_1{
background-color: red;
position: absolute;
left: 50px;
top: 50px;
}
.absolute_2{
background-color: gold;
}
.fixed_1,.fixed_2{
height: 100px;
width: 400px;
margin-top :10px;
font-size: 16px;
}
.fixed_1{
background-color: red;
position: fixed;
left: 50%;
margin-left: -200px; /* 設置水平垂直居中*/
top: 50%;
margin-top: -50px;
z-index: 9999; /* 彈框一般設置足夠大的值 */
}
/* 彈框效果 */
.mask{
position: fixed;
width: 100%;
height: 100%;
background-color: black;
opacity: 0.6; /* 設置透明度 */
z-index: 9998; /* 一般比彈框設置的值小1*/
}
.fixed_2{
background-color: gold;
}
.static_1{
height: 100px;
width: 400px;
margin-top :10px;
background-color: gold;
position: static;
}
</style>
</head>
<body>
<div class="mask">
</div>
<div class="con1">
<div class="no_pos_1">
沒有使用定位
</div>
<div class="no_pos_2">
沒有使用定位
</div>
</div>
<div class="con2">
<div class="relative_1">
relative 生成相對定位元素,元素所占據的文檔流的位置保留,元素本身相對自身原位置進行偏移
</div>
<div class="relative_2">
</div>
</div>
<div class="con3">
<div class="absolute_1">
absolute 生成絕對定位元素,元素脫離文檔流,不占據文檔流的位置,可以理解為漂浮在文檔流的上方,相對于上一個設置了定位的父級元素來進行定位,如果找不到,則相對于body元素進行定位
</div>
<div class="absolute_2">
</div>
</div>
<div class="con4">
<div class="fixed_1">
fixed 生成固定定位元素,元素脫離文檔流,不占據文檔流的位置,可以理解為漂浮在文檔流的上方,相對于瀏覽器窗口進行定位。
</div>
<div class="fixed_2">
</div>
</div>
<div class="con5">
<div class="static_1">
static 默認值,沒有定位,元素出現在正常的文檔流中,相當于取消定位屬性或者不設置定位屬性。
</div>
</div>
</body>
</html>123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143復制代碼類型:[html]
制作一個彈框效果:
「鏈接」
HP是世界上最好的語言,這是一個老梗。
有不少學習PHP的程序員后來去做了前端開發,畢竟近些年前端開發還是蠻吃香的。
學習PHP不僅僅要學習html,而且還要學習CSS。
CSS是萬維網聯盟在 HTML 4.0 之外提出,目的是為了讓CSS完成樣式與內容的分離。
那么,CSS如何入門呢?w3cschool在這里分享幾個方法:
w3cschool官方本身就有CSS教程,我們看教程的目的主要還是要了解CSS到底是干什么用的。
其實,用一句簡單的話來說,改變我們看的網頁的樣子.。
w3cschool新開發了CSS微課,這可能是很多程序員小伙伴所需要的。
CSS直接抓住了CSS教程中比較核心的一些概念和語法,并且有實戰的訓練習題。
其內容包括了CSS基礎、CSS文本樣式、CSS屬性、CSS定位和布局,讓你系統、立體地全面認識CSS。
CSS微課實現了游戲化的編程體驗,關卡是循序漸進的,這迫使你不能跳躍而忽略一些重要的編程知識。
其中,習題類型包含了判斷題、選擇題、實戰訓練題。
理論離不開實戰,CSS微課做到了例子多,概括技術全面。
當你可以通關的時候,你已經對CSS算是有一個比較深刻的認識,也掌握了一定的CSS編程技能。
學編程一定要讓編程本身變得有趣,所以大可以先玩編程。
用CSS微課學習是一種有趣化的方法。
另外,閱讀《css禪意花園》,就當成一本故事書看,隨便翻翻你會發現css確實很好玩的。
文來源于:程序員成長指北;作者:海闊_天空
原文:https://juejin.cn/post/7173596154297810957
如有侵權,聯系刪除
如何快速定位線上bug,是多數開發者都會遇到的難題
web-see[1] 前端監控方案,提供了 前端錄屏+定位源碼 方式,讓bug無處藏身
這是前端監控的第二篇,該篇講解如何實現錯誤還原功能,第一篇 從0到1搭建前端監控平臺,面試必備的亮點項目總結(已開源)沒有看過的小伙伴,建議先了解下
在監控后臺,通過報錯信息列表,可以查看具體報錯的源碼,以及報錯時的錄屏回放
效果演示:
錄屏記錄了用戶的所有操作,紅色的線代表了鼠標的移動軌跡
前端項目發布上線,代碼一般都會進行壓縮、混淆、甚至加密,當線上代碼報錯時,很難定位到具體的源碼
SourceMap 完美解決了代碼反解的問題,項目在打包時,除了生成最終 XXX.js 文件外,還會額外生成一個 XXX.js.map 的文件
.map 文件里包含了原始代碼及其映射信息,可以利用它反解出報錯信息的源碼
先了解下 SourceMap 的基本內容
例如 app.a2a3ceec.js 代碼如下:
var add=function(x, y){return x+y;};
//# sourceMappingURL=app.a2a3ceec.js.map
復制代碼
其中 sourceMappingURL 用來說明該文件對應的map文件
對應的 app.a2a3ceec.js.map 代碼如下:
{
version : 3, // SourceMap標準版本,最新的為3
file: "js/app.a2a3ceec.js", // 轉換后的文件名
sourceRoot : "", // 轉換前的文件所在目錄,如果與轉換前的文件在同一目錄,該項為空
sources: [ // 轉換前的文件,該項是一個數組,表示可能存在多個文件合并
"webpack://web-see-demo/./src/App.vue",
"webpack://web-see-demo/./src/main.js"
],
names: [], // 轉換前的所有變量名和屬性名
sourcesContent: [ // 原始文件內容
"const add = (x,y) => {\n return x+y;\n}"
],
// 所有映射點
mappings: "AAAA,IAAM,GAAG,GAAG,UAAC,CAAQ,EAAC,CAAQ;IAC5B,OAAO,CAAC,GAAC,CAAC,CAAC;AACb,CAAC,CAAA"
}
復制代碼
其中 sources 和 sourcesContent 是關鍵字段,下文的還原示例中將用到
代碼還原,這里主要使用 source-map-js[3] 庫,下面介紹下如何使用
示例代碼:
import sourceMap from 'source-map-js';
/**
* findCodeBySourceMap用于獲取map文件對應的源代碼
* @param { string } fileName .map文件名稱
* @param { number } line 發生錯誤的行號
* @param { number } column 發生錯誤的列號
* @param { function } 回調函數,返回對應的源碼
*/
const findCodeBySourceMap = async ({ fileName, line, column }, callback) => {
// loadSourceMap 用于獲取服務器上 .map 的文件內容
let sourceData = await loadSourceMap(fileName);
let { sourcesContent, sources } = sourceData;
// SourceMapConsumer實例表示一個已解析的源映射
// 可以通過在生成的源中給它一個文件位置來查詢有關原始文件位置的信息
let consumer = await new sourceMap.SourceMapConsumer(sourceData);
// 輸入錯誤的發生行和列,可以得到源碼對應原始文件、行和列信息
let result = consumer.originalPositionFor({
line: Number(line),
column: Number(column)
});
// 從sourcesContent得到具體的源碼信息
let code = sourcesContent[sources.indexOf(result.source)];
……
callback(code)
復制代碼
本小節的代碼倉庫[4]
source-map 的還原流程:
1、從服務器獲取指定.map 的文件內容
2、new 一個 SourceMapConsumer 的實例,表示一個已解析的源映射,給它一個文件位置來查詢有關原始文件位置的信息
3、輸入報錯發生的行和列,可以得到源碼對應原始文件名、行和列信息
4、從源文件的 sourcesContent 字段中,獲取對應的源碼信息
接下來的重點就變為:如何獲取報錯發生的原始文件名、行和列信息
通過第一篇文章的介紹,我們知道可以通過多種方式來捕獲報錯
比如 error事件、unhandledrejection事件、vue 中通過Vue.config.errorHander、react中通過componentDidCatch
為了消除各瀏覽器的差異,使用 error-stack-parser[5] 庫來提取給定錯誤的原始文件名、行和列信息
示例代碼:
import ErrorStackParser from 'error-stack-parser';
ErrorStackParser.parse(new Error('BOOM'));
// 返回值 StackFrame 堆棧列表
[
StackFrame({functionName: 'foo', args: [], fileName: 'path/to/file.js', lineNumber: 35, columnNumber: 79, isNative: false, isEval: false}),
StackFrame({functionName: 'Bar', fileName: 'https://cdn.somewherefast.com/utils.min.js', lineNumber: 1, columnNumber: 832, isNative: false, isEval: false, isConstructor: true}),
StackFrame(... and so on ...)
]
復制代碼
這里簡單說明下 JS 堆棧列表
堆棧示例:
function c() {
try {
var bar = baz;
throw new Error()
} catch (e) {
console.log(e.stack);
}
}
function b() {
c();
}
function a() {
b();
}
a();
復制代碼
上述代碼中會在執行到 c 函數的時候報錯,調用棧為 a -> b -> c,如下圖所示:
一般我們只需要定位到 c 函數的堆棧信息,所以使用 error-stack-parser 庫的時候,只取 StackFrame 數組中的第一個元素
最終代碼:
import ErrorStackParser from 'error-stack-parser';
// 取StackFrame數組中的第一個元素
let stackFrame = ErrorStackParser.parse(error)[0];
// 獲取對應的原始文件名、行和列信息,并上報
let { fileName, columnNumber, lineNumber } = stackFrame;
復制代碼
下載 web-see-demo[6] 安裝并運行
1)點擊 js錯誤 按鈕,會執行 HomeView.vue 文件中的 codeErr 方法
codeErr的源碼為:
codeErr.png
2)Vue.config.errorHander中捕獲到報錯信息為:
length.png
3)使用 ErrorStackParser.parse 解析后的stackFrame為:
stackFrame.png
4)經過 consumer.originalPositionFor 還原后的 result 結果為:
result.png
5)最終拿到的源碼:
code.png
sourcemap.png
如上圖所示,定位源碼流程總結:
1、項目中引入監控 SDK,打包后將js文件發布到服務器上
2、將 .map 文件放到指定的地址,統一存儲
3、當線上代碼報錯時,利用 error-stack-parser 獲取具體原始文件名、行和列信息,并上報
4、利用 source-map 從 .map 文件中得到對應的源碼并展示
web-see 監控通過 rrweb[7] 提供了前端錄屏的功能
先介紹下在vue中如何使用
錄制示例:
import { record } from 'rrweb';
// events存儲錄屏信息
let events = [];
// record 用于記錄 `DOM` 中的所有變更
rrweb.record({
emit(event, isCheckout) {
// isCheckout 是一個標識,告訴你重新制作了快照
if (isCheckout) {
events.push([]);
}
events.push(event);
},
recordCanvas: true, // 記錄 canvas 內容
checkoutEveryNms: 10 * 1000, // 每10s重新制作快照
checkoutEveryNth: 200, // 每 200 個 event 重新制作快照
});
復制代碼
播放示例:
<template>
<div ref='player'>
</div>
</template>
<script>
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
export default {
mounted() {
// 將記錄的變更按照對應的時間一一重放
new rrwebPlayer(
{
target: this.$refs.player, // 回放所需要的HTML元素
data: { events }
},
{
UNSAFE_replayCanvas: true // 回放 canvas 內容
}
)
}
}
</script>
復制代碼
rrweb 主要由 rrweb 、 rrweb-player 和 rrweb-snapshot 三個庫組成:
1)rrweb:提供了 record 和 replay 兩個方法;record 方法用來記錄頁面上 DOM 的變化,replay 方法支持根據時間戳去還原 DOM 的變化
2)rrweb-player:基于 svelte 模板實現,為 rrweb 提供了回放的 GUI 工具,支持暫停、倍速播放、拖拽時間軸等功能。內部調用了 rrweb 的提供的 replay 等方法
3)rrweb-snapshot:包括 snapshot 和 rebuilding 兩大特性,snapshot 用來序列化 DOM 為增量快照,rebuilding 負責將增量快照還原為 DOM
rrweb 整體流程:
1)rrweb 在錄制時會首先進行首屏 DOM 快照,遍歷整個頁面的 DOM 樹,轉換為 JSON 結構數據,使用增量快照的處理方式,通過 mutationObserver 獲取 DOM 增量變化,同步轉換為 JSON 數據進行存儲
2)整個錄制的過程會生成 unique id,來確定增量數據所對應的 DOM 節點,通過 timestamp 保證回放順序。
3) 回放時,會創建一個 iframe 作為承載事件回放的容器,針對首屏 DOM 快照進行重建,在遍歷 JSON 的同時,根據序列化后的節點數據構建出實際的 DOM 節點
4)rrweb 可以監聽的用戶行為包括:鼠標移動,鼠標交互,頁面滾動,視窗變化、用戶輸入等,通過添加相應的監聽事件來實現
如果一直錄屏,數據量是巨大的
實測下來,錄制10s的時長,數據大小約為 8M 左右(頁面的不同復雜度、用戶不同操作的頻率都會造成大小不一樣)
數據如果不經過壓縮,直接傳給后端,面對大量的用戶,需要非常高的帶寬做支持。還好,rrweb官方提供了數據壓縮函數[8]
基于 packFn 的單數據壓縮,在錄制時可以作為 packFn 傳入
rrweb.record({
emit(event) {},
packFn: rrweb.pack,
});
復制代碼
回放時,需要傳入 rrweb.unpack 作為 unpackFn 傳入
const replayer = new rrweb.Replayer(events, {
unpackFn: rrweb.unpack,
});
復制代碼
但是官方提供的壓縮方式,是對每個 event 數據單獨進行壓縮,壓縮比不高。實測下來,壓縮比在70%左右,比如原來 8M 的數據,壓縮后為 2.4M 左右
官方更加推薦將多個 event 批量一次性壓縮,這樣壓縮效果更好
web-see 內部使用 pako.js[9]、js-base64[10] 相結合的壓縮方式,實測下來,壓縮比為 85% 以上,原來 8M 的數據,壓縮后為 1.2M 左右
壓縮代碼示例:
import pako from 'pako';
import { Base64 } from 'js-base64';
// 壓縮
export function zip(data) {
if (!data) return data;
// 判斷數據是否需要轉為JSON
const dataJson = typeof data !== 'string' && typeof data !== 'number' ? JSON.stringify(data) : data;
// 使用Base64.encode處理字符編碼,兼容中文
const str = Base64.encode(dataJson);
let binaryString = pako.gzip(str);
let arr = Array.from(binaryString);
let s = '';
arr.forEach((item) => {
s += String.fromCharCode(item);
});
return Base64.btoa(s);
}
復制代碼
解壓代碼示例:
import { Base64 } from 'js-base64';
import pako from 'pako';
// 解壓
export function unzip(b64Data) {
let strData = Base64.atob(b64Data);
let charData = strData.split('').map(function (x) {
return x.charCodeAt(0);
});
let binData = new Uint8Array(charData);
let data = pako.ungzip(binData);
// ↓切片處理數據,防止內存溢出報錯↓
let str = '';
const chunk = 8 * 1024;
let i;
for (i = 0; i < data.length / chunk; i++) {
str += String.fromCharCode.apply(null, data.slice(i * chunk, (i + 1) * chunk));
}
str += String.fromCharCode.apply(null, data.slice(i * chunk));
// ↑切片處理數據,防止內存溢出報錯↑
const unzipStr = Base64.decode(str);
let result = '';
// 對象或數組進行JSON轉換
try {
result = JSON.parse(unzipStr);
} catch (error) {
if (/Unexpected token o in JSON at position 0/.test(error)) {
// 如果沒有轉換成功,代表值為基本數據,直接賦值
result = unzipStr;
}
}
return result;
}
復制代碼
一般關注的是,頁面報錯的時候用戶做了哪些操作,所以目前只把報錯前10s的錄屏上報到服務端
如何只上報報錯時的錄屏信息呢 ?
1)window上設置 hasError、recordScreenId 變量,hasError用來判斷某段時間代碼是否報錯;recordScreenId 用來記錄此次錄屏的id
2)當頁面發出報錯需要上報時,判斷是否開啟了錄屏,如果開啟了,將 hasError 設為 true,同時將 window 上的 recordScreenId,存儲到此次上報信息的 data 中
3)rrweb 設置10s重新制作快照的頻率,每次重置錄屏時,判斷 hasError 是否為 true(即這段時間內是否發生報錯),有的話將這次的錄屏信息上報,并重置錄屏信息和 recordScreenId,作為下次錄屏使用
4)后臺報錯列表,從本次報錯報的data中取出 recordScreenId 來播放錄屏
錄屏的代碼示例:
handleScreen() {
try {
// 存儲錄屏信息
let events = [];
record({
emit(event, isCheckout) {
if (isCheckout) {
// 此段時間內發生錯誤,上報錄屏信息
if (_support.hasError) {
let recordScreenId = _support.recordScreenId;
// 重置recordScreenId,作為下次使用
_support.recordScreenId = generateUUID();
transportData.send({
type: EVENTTYPES.RECORDSCREEN,
recordScreenId,
time: getTimestamp(),
status: STATUS_CODE.OK,
events: zip(events)
});
events = [];
_support.hasError = false;
} else {
// 不上報,清空錄屏
events = [];
_support.recordScreenId = generateUUID();
}
}
events.push(event);
},
recordCanvas: true,
// 默認每10s重新制作快照
checkoutEveryNms: 1000 * options.recordScreentime
});
復制代碼
按照官方的 canvas 配置,驗證下來,rrweb 還是不支持 canvas 的錄制,比如使用 echarts 畫圖,圖形區域的錄屏顯示是空白的
官方配置[11] 如下:
Canvas.png
測試demo[12] 如下:
echart.png
錄屏回放,圖形這塊區域是空白的:
canvas.gif
*請認真填寫需求信息,我們會在24小時內與您取得聯系。