Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
1 wang編輯器效果圖
1、npm安裝
安裝過程比較簡單,不做重復,說一下使用過程遇到的問題
插入hr
import E from 'wangeditor'
mounted () {
const editor = new E('#div1')
const menuKey = 'hrMenuKey'
const { BtnMenu } = E
// 第一,菜單 class ,Button 菜單繼承 BtnMenu class
class HrMenu extends BtnMenu {
constructor (editor) {
// data-title屬性表示當鼠標懸停在該按鈕上時提示該按鈕的功能簡述
const $elem = E.$(
`<div class="w-e-menu" data-title="分割線">
<i class='w-e-icon-split-line'></i>
</div>`
)
super($elem, editor)
}
// 菜單點擊事件
clickHandler () {
editor.cmd.do('insertHtml', '<hr>')
}
tryChangeActive () {
// 激活菜單
// 1. 菜單 DOM 節點會增加一個 .w-e-active 的 css class
// 2. this.this.isActive === true
this.active()
// // 取消激活菜單
// // 1. 菜單 DOM 節點會刪掉 .w-e-active
// // 2. this.this.isActive === false
// this.unActive()
}
}
// 注冊菜單
E.registerMenu(menuKey, HrMenu)
editor.config.placeholder = ''
editor.config.uploadImgServer = '/public/sss/admin.php/ajaxweb/uppic.html'
editor.config.uploadImgMaxSize = 1024 * 1024
editor.config.uploadImgAccept = ['jpg', 'jpeg', 'png', 'gif']
editor.config.height = 300
editor.config.focus = true
editor.config.menus = [
'source',
'head',
'bold',
'fontSize',
'fontName',
'italic',
'underline',
'strikeThrough',
'indent',
'lineHeight',
'foreColor',
'backColor',
'link',
'list',
'justify',
'quote',
'image',
'video',
'table',
'undo',
'redo']
editor.create()
}
查看源碼
圖2 查看源碼效果圖
實現目標:
點擊查看的時候,遮蓋其它的按鈕,防止查看源碼的時候,點擊了別的按鈕進行了誤操作。
新加的菜單默認都是在最后全屏前邊,分割線還可以,但是查看源碼我個人還是習慣在最前邊,使用的是jquery prepend感覺更加簡單一些,代碼如下:
import $ from 'jquery'
mounted () {
$(document).ready(function () {
$('#div1 .w-e-toolbar').prepend('<div class=\'w-e-menu\' style=\'z-index:991;\' data-title=\'查看源碼\'><a style=\' display:block;width:100%;height:100%;\' ct=1 id=\'viewsource\'><i class=\'fa fa-file-text-o\'></i></a></div>')
$(document).delegate('#viewsource', 'click', function () {
var editorHtml = editor.txt.html()
// console.log($(this).attr('ct'))
if (parseInt($(this).attr('ct')) === 1) {
$('#div1 .w-e-toolbar').prepend('<div id=\'zzc\' style=\'position:absolute;left:0;top:0;z-index:99;background-color:rgba(0,0,0,0.5);width:100%;height:40px;\'></div>')
$(this).parent().parent().parent().find('.w-e-text').css('width', $('.w-e-text').width() + 'px')
editorHtml = editorHtml.replace(/</g, '<').replace(/>/g, '>').replace(/ /g, ' ')
$(this).attr('ct', '2')
$(this).css({'background-color': '#EEE'})
} else {
editorHtml = editor.txt.text().replace(/</ig, '<').replace(/>/ig, '>').replace(/ /ig, ' ')
$(this).attr('ct', '1')
$(this).parent().parent().parent().find('.w-e-text').css('width', '100%')
$(this).parent().parent().find('#zzc').remove()
$(this).css({'background-color': '#FFF'})
}
editor.txt.html(editorHtml)
// editor.change && editor.change()
})
})
}
說明:
彈出的窗口,點擊關閉無效不能關閉
圖3 彈出菜單無法關閉
如圖,點擊關閉的時候會切換到了網絡圖片的表單,這應該是菜單同級別,遮蓋了關閉的按鈕,所以我們要寫css樣式加上z-index使關閉的菜單在其他的上層;
css代碼
.w-e-icon-close{
display:block;
z-index:999999 !important;
}
2、cdn引用js
我是下載到本地的
圖4 多圖上傳F12查看效果
圖片上傳,如果選擇多個圖片,可能會出現以上圖片的情況style="font-size: 14px; font-family: "Helvetica Neue", Helvetica, "PingFang SC", Tahoma, Arial, sans-serif; max-width: 100%;"
復制html發現是如下代碼:
<img src="/public/upload/image/20211201/111.jpg" style="font-size: 14px; font-family: & quot;Helvetica Neue& quot;, Helvetica, & quot;PingFang SC& quot;, Tahoma, Arial, sans-serif; max-width: 100%;">
很明顯style內的 css樣式,不是應該出現的,沒有找到在哪里修改。
雙引號換成了& quot;如果不用查看源碼,頁面直接顯示沒有問題,但是查看源碼,切換回來以后,會發現圖片亂了,所以查看源碼的時候,需要把& quot;替換成空。
以下使用jquery實現自定義菜單(查看源碼、插入分割線):
import $ from 'jquery'
mounted () {
$(document).ready(function () {
// 查看源碼菜單
$('#div1 .w-e-toolbar').prepend('<div class=\'w-e-menu\' style=\'z-index:991;\' data-title=\'查看源碼\'><a style=\' display:block;width:100%;height:100%;\' ct=1 id=\'viewsource\'><i class=\'fa fa-file-text-o\'></i></a></div>')
// 分割線菜單
var menl=$('#div1 .w-e-toolbar .w-e-menu').length;
$("#div1 .w-e-toolbar .w-e-menu").eq(menl-1).before('<div class=\'w-e-menu\' data-title=\'分割線\'><a style=\'display:block;width:100%;height:100%;\' id=\'splitline\'><i class=\'w-e-icon-split-line\'></i></a></div>')
// 查看源碼點擊
$(document).delegate('#viewsource', 'click', function () {
var editorHtml = editor.txt.html()
// console.log($(this).attr('ct'))
if (parseInt($(this).attr('ct')) === 1) {
$('#div1 .w-e-toolbar').prepend('<div id=\'zzc\' style=\'position:absolute;left:0;top:0;z-index:99;background-color:rgba(0,0,0,0.5);width:100%;height:40px;\'></div>')
$(this).parent().parent().parent().find('.w-e-text').css('width', $('.w-e-text').width() + 'px')
editorHtml = editorHtml.replace(/</g, '<').replace(/>/g, '>').replace(/ /g, ' ').replace(/& quot;/g, '')
$(this).attr('ct', '2')
$(this).css({'background-color': '#EEE'})
} else {
editorHtml = editor.txt.text().replace(/</ig, '<').replace(/>/ig, '>').replace(/ /ig, ' ')
$(this).attr('ct', '1')
$(this).css('border', '0px solid #DDD')
$(this).parent().parent().parent().find('.w-e-text').css('width', '100%')
$(this).parent().parent().find('#zzc').remove()
$(this).css({'background-color': '#FFF'})
}
editor.txt.html(editorHtml)
// editor.change && editor.change()
})
//分割線插入hr點擊
$(document).delegate('#splitline', 'click', function () {
editor.cmd.do('insertHtml', '<hr>');
});
})
}
如果我們把插入分割線的代碼單獨拿出來,只是下面幾行,相對使用官方提供的方法會簡單很多
// 插入分割線菜單
var menl=$('#div1 .w-e-toolbar .w-e-menu').length;
$('#div1 .w-e-toolbar .w-e-menu').eq(menl-1).before('<div class=\'w-e-menu\' data-title=\'分割線\'><a style=\'display:block;width:100%;height:100%;\' id=\'splitline\'><i class=\'w-e-icon-split-line\'></i></a></div>')
// 分割線插入 hr點擊事件
$(document).delegate('#splitline', 'click', function () {
editor.cmd.do('insertHtml', '<hr>');
});
說明:
editorHtml = editorHtml.replace(/</g, '<').replace(/>/g, '>').replace(/ /g, ' ').replace(/& quot;/g, '')
附錄一下jquery after,before,append,prepend用法:
向class=w-e-toolbar的div頭部插入html
$(" .w-e-toolbar").prepend("<div >頭部插入html</div>")
向class=w-e-toolbar的div底部插入html
$(" .w-e-toolbar").append("<div >底部插入html</div>")
向class=w-e-toolbar的div之前插入html
$(" .w-e-toolbar").before("<div >之前插入html</div>")
向class=w-e-toolbar的div之后插入html
$(" .w-e-toolbar").after("<div >后面插入html</div>")
可以做一下延伸:像我們上邊用到的向第幾個元素之后或者之前插入代碼。
隨著用于訪問萬維網的設備的數量和種類呈指數級增長,無論用于瀏覽網頁的設備如何,確保正確呈現網頁都是一項重要且具有挑戰性的任務。當開發人員采用響應式網頁設計(RWD)技術時,網頁會修改其外觀以適應設備的顯示限制。但是,目前缺乏自動化支持意味著在針對不同窗口大小進行渲染時,可能無法檢測到頁面布局中的顯示故障。一個核心問題是難以提供自動Oracle來驗證RWD布局,這意味著在實踐中檢查故障主要是手動過程,這導致許多實時響應式網站的布局故障。本文介紹了一種自動故障檢測技術,該技術可檢查響應頁面布局在一系列窗口寬度上的一致性,從而避免了對顯式Oracle的需求。在一項實證研究中,該方法在所研究的26個真實產品頁面中的16個中發現了故障,總共檢測到33個不同的故障。
響應式網頁設計,顯示故障,布局故障
響應式網頁設計(RWD)是最近的一種設計和實現方法,它使開發人員能夠構建無論設備大小如何都能提供等效用戶體驗的網頁。RWD使網頁能夠動態地修改其布局以適應或“響應”設備顯示器的大小,而不是要求用戶平移太寬而不適合較小屏幕的頁面,或縮放在桌面顯示器上清晰可讀,但在手機上太小的部分。如果內容多于可用空間,則用戶只需垂直滾動頁面。因此,在RWD的上下文中,瀏覽器窗口寬度是關于網頁布局應如何調整的關鍵決定因素。
鑒于RWD的明顯優勢,自動檢查顯示故障的問題——網頁渲染中的視覺差異導致其偏離其預期外觀——是一個重要的問題。由于網站的美學和布局已被證明會影響其感知的可用性[26]和可訪問性,提高其可信度,并產生用戶忠誠度,因此在一個組織的網站中可見的故障可能導致收入損失并不令人驚訝。
針對這些問題,我們提出了一種自動化技術,可以檢測在實際生產頁面中普遍存在的五種類型的響應式布局故障,而無需顯式的Oracle,例如一系列模型映像,復雜的布局規范或要比較的頁面的圖形模型。我們的方法依賴于常見的響應故障類型的隱式Oracle知識,自動檢查響應式網頁的布局,并在不同的窗口寬度上比較元素相對于彼此的位置。
例如,兩個網頁元素可能由于預期的圖形效果而重疊,或者因為它們已經“碰撞”,因為水平屏幕空間已經減少。我們的方法通過檢查連續窗口寬度的布局行為來區分兩者。如果元素總是重疊,則開發人員在手動檢查中可能意圖和/或容易注意到該效果。然而,如果元素經常重疊,則可能發生微妙的RLF。我們的方法應用類似的原則來檢測不一致的元素包裝;僅存在一個或兩個窗口寬度的布局;和內容的間歇性突出到其他元素,或完全從窗口出來??偟膩碚f,我們定義了四種檢測這五種RLF的算法。
我們將自動化技術應用于26個真實產品網頁。實驗表明,我們的方法可以在16個網頁中找到故障,總共檢測到33個不同的故障。我們的評估進一步表明,在當前可用工具的幫助下應用手動檢查過程錯過了我們的技術可以自動檢測的19%至34%的RLF。
總結來說,本位的重要貢獻如下:
(1)可發現的五種不同類型的響應布局故障(RLF)的分類,而不需要明確的Oracle。
(2)四種算法,可以自動檢測響應設計的網頁中的五種類型的布局故障。
(3)包含26個隨機選擇的產品網頁的實證研究,顯示所識別的RLF類型在實時站點中普遍存在,并且我們的算法能夠檢測它們,總共發現了33個不同的故障。
本節定義了五種不同類型的響應式布局故障(RLF),它們對于RWD都是有問題的,并且可以通過不需要顯式Oracle的算法自動識別,例如網頁的替代參考版本,模型圖像或布局約束的復雜規范。
相反,我們的方法通過自動提取網頁響應布局的模型,然后通過在不同的窗口寬度交叉檢查其布局來分析該模型的潛在故障。我們首先介紹網頁模型(第3.1節)。然后,我們介紹了響應式網頁中普遍存在的五種類型的RLF,以及可用于通過網頁模型檢測它們的四種算法的定義(第3.2節)。
2.1基本概念:rRLG
我們的RLF檢測過程的基礎是頁面的響應布局模型,它是響應布局圖(RLG)的改進。RLG不同于網頁的其他布局圖模型(例如,Choudhary等人的“對齊圖”[38]和Alameer等人的“布局圖”),因為它模擬了一個窗口寬度范圍內的網頁——而不是一個靜態寬度——以捕獲其響應式設計。
通過查詢網頁的文檔對象模型(DOM)來自動獲得RLG,以在不同的窗口寬度處找到頁面中涉及的HTML元素及其坐標。RLG根據其響應式設計,組織此信息以跟蹤這些HTML元素的動態可見性和相對對齊,因為頁面布局根據窗口寬度進行調整?!熬芌LG”(rRLG)與我們原始的RLG的不同之處在于它不通過“寬度約束”對網頁元素的寬度進行建模。雖然設計用于在頁面中捕獲回歸問題,但寬度約束無助于檢測本文中介紹的五種常見類型的RLF;因此rRLG不對它們進行建模。
圖2提供了一個rRLG的示例,用于通過線框在兩個不同的窗口寬度上繪制的網頁。該頁面涉及HTML元素div[1]-div[3],這些元素相互堆疊用于窄窗口,要求用戶滾動以將每個視圖放入視圖中。對于更寬的窗口,它們并排排列,并附有橫幅圖像img。
2.2響應式布局故障的常見類型及檢測算法
一旦為響應式網頁創建了rRLG,就可以檢查我們接下來介紹的五種RLF中的每種類型以及可用于檢測它們的算法。 每種算法的目的不僅是識別故障,還要識別涉及哪些HTML元素以及特定窗口寬度,以幫助開發人員診斷故障。
RLF1:元素碰撞。當響應設計的頁面的窗口變得更窄時,考慮寬度損失的一種設計策略是將水平對齊的頁面元素移動得更靠近。但是,當窗口收縮時,元素可能會相互碰撞,導致其內容重疊。這可能導致意外的效果,例如重疊的文本或圖像,或隱藏的內容或功能元素,從而損害頁面可用性。
圖1和MidwayMeetup頁面顯示了一個示例。在更寬的窗口(第1d部分),輸入框和按鈕并排存在。然而,當窗口變得太窄時,元素發生碰撞,使最左邊的按鈕變暗(第1c部分)。
RLF2:元素突出。在實現響應式設計時,一個問題是確保隨著窗口變得更窄,HTML元素的大小也會適應,因此它們仍然足以容納其內容。當元素沒有正確調整大小時,它們的內容可能不再“適合”,因此會突出到頁面的周圍部分。圖1的PDFescape示例及其導航鏈接塊顯示在頁面的右上角(第1f部分)。隨著窗口變窄,水平空間不再足以適合徽標旁邊的鏈接塊。鏈接突出顯示包含的HTML元素,對用戶(第1e部分)不可見,因為容器具有CSS屬性“overflow: hidden”集。因此,鏈接變得不可點亮,并且頁面在特定大小的設備上是不可用的,并且窗口尺寸是固定的。
RLF3:窗口突出。當窗口空間被擠壓時,元素可能不僅開始溢出其容器,而且還開始從頁面的根表示HTML元素(即身體標簽)中突出,從而出現在水平可視部分之外。這頁紙。如第2節所介紹的圖1的ConsumerReports網頁展示了這種故障類型
RLF4:小范圍布局。響應式設計的網站傾向于使用許多CSS規則,這些規則由不同的媒體查詢激活和停用。對于給定的窗口寬度,CSS規則中的多個媒體查詢可以同時評估為真。例如,兩個規則,一個在窗口寬度超過768像素時激活,另一個規則在窗口低于1024像素時激活,將在769-1023像素范圍內激活。對于給定的元素集和窗口大小,當一系列規則“開啟”或“關閉”時,管理的邏輯可能很快變得復雜,開發人員可能經常犯錯誤導致在窗口中應用CSS規則寬度與意想的不同。當開發人員混合使用最小寬度和最大寬度限定符來定義布局中的更改時,就會出現這種類型的常見錯誤。例如,開發人員可以編碼媒體查詢“@media(max-width:768px){...} “和另一個”@media(min-width:768px){...}”。由于這兩個表達式定義的窗口范圍是包含的,因此兩者都將在768像素窗口寬度處激活。媒體查詢的這種沖突可能導致奇怪的布局效果,因為當只有一個集合時,將激活兩組規則。開發人員難以發現這些類型的故障,因為它們僅出現在可以查看頁面的整個窗口寬度范圍的小范圍內。圖1的Cloudconvert示例是小范圍RLF的示例。在單個窗口寬度處,頁面的頂部菜單模糊了公司的徽標和標語(第1g部分)。
RLF5:包裝元素。如果網頁上的包含元素不足以容納其子元素,但仍然“足夠”或具有靈活的高度,則其中包含的水平對齊元素將“換行”以形成其他元素,不受歡迎的元素行 - 以及不需要的表現效果。圖1和BugMeNot網頁顯示了錯誤包裝的示例。在較寬的窗口(第1j部分),放大鏡按鈕出現在搜索框旁邊。然而,隨著窗口變窄,按鈕會包裹到下一行(第1i部分)。
為了評估我們技術的有效性和效率,我們將其應用于生產使用的26個真實網頁,最終目的是回答以下三個研究問題:
RQ1:我們的算法在檢測故障方面的效果如何?
我們的算法在響應式設計頁面中檢測常見類型的響應式布局故障的效果如何?
RQ2:使用“spotchecking”工具可以檢測到多少次故障?
在窗口大小調整工具的幫助下,與我們的方法相比,“抽樣檢查”過程的效果如何?
RQ3:應用于響應式設計的網頁時,我們的技術需要運行多長時間?
對于將在實際的RWD網頁開發過程中應用該技術的開發人員來說,檢測布局故障的時間是否合理?
RQ1:表1b使用每種檢測算法分解了ReDeCheck針對每個網頁生成的故障報告的分類。我們的每個算法都發現了TP(即實際的RLF),26個受試者中有16個至少有一個TP。鑒于對象是包括Duolingo和StumbleUpon等已建立的商業運營的實時和運營網站,這是一個令人信服的結果,因為我們推測,這些網站將進行內部測試過程,錯過了由ReDeCheck。我們的算法報告的五個故障是我們用于激勵示例的故障。這些故障是消費者端口離屏突出的內容示例(圖1a,算法2檢測到);表單元素相互遮擋,降低了MidwayMeetup的功能(圖1c,由算法1檢測到);導航鏈接由于其父元素為PDFescape的突出而消失(圖1e,也被算法1檢測到);為Cloudconvert打破媒體查詢和模糊布局(圖1g,由算法3檢測),以及BugMeNot的包裝元素(圖1i,由算法4檢測)。
TP的進一步分析顯示,這些報告總共匯總了33個不同的故障,如表1b的“Distinct RLFs”欄所報告,從而揭示了ReDeCheck結果的重復程度。一個極端的例子是Accountkiller。在這里,由算法3生成的147個小范圍報告是TP,但更仔細的分析顯示所有報告都對應于單個明顯的故障。該頁面涉及圖標網格,每個圖標對應于rRLG節點,其中對齊約束連接每對。對于一個小窗口范圍,ReDeCheck檢測網格中未按一致排列的元素,導致相對對齊的變化和一些小范圍約束,這些約束導致算法觸發每個單獨的故障報告。另外,可以通過不同的算法檢測相同的不同故障。例如,對于Cloudconvert,元素僅在小范圍內發生碰撞,從而觸發算法1和算法3的報告。
RQ2:Spotcheck分析顯示我們的方法已經發現RLF作為RQ1的一部分,但沒有新的額外故障。表2報告了在每個工具建議的窗口寬度之一處或通過隨機選擇窗口寬度而顯示的這些不同RLF的數量。如表所示,工具顯示了66%到81%的這些故障,具體取決于他們建議檢查的窗口寬度集。由于這些預設寬度對應于常用設備,因此該結果顯示ReDeCheck能夠顯示將在這些設備上顯示的故障以供用戶查看。盡管點測工具提示了檢查網頁的窗口寬度,但仍然需要手動識別故障——相比之下,Re-DeCheck減輕了開發人員的一些努力。結果還表明,我們的技術確定的故障率為19%至34%。即使使用了所有的抽查工具,并通過一定程度的進一步隨機抽查來補充,我們的方法最初確定的五種不同的RLF也找不到。
RQ3:圖3顯示了ReDeCheck在30個試驗中每個網頁的執行時間中位數。幾乎所有頁面(26個中的25個)都是由我們的工具在中位數大約三分鐘內處理的,15頁需要60秒或更少。 Airbnb花了將近4.5分鐘——但它是最復雜的對象頁面之一,如表1a所示。一般來說,圖表顯示,花費最多時間的對象要么是最復雜的一部分(即,就CSS元素而言是Airbnb,而在CSS聲明方面是Duolingo),或者產生最多的問題(即Accountkiller和PepFeed),因此,在捕獲故障屏幕截圖和生成帶注釋的圖形報告(針對TP,FP和NOI)或者網頁復雜性和報告的問題數量(即,ConsumerReports)的混合時,會產生額外的時間成本。
響應式網頁設計(RWD)倡導創建具有跨多個窗口寬度的增強用戶體驗的網頁[32]。由于根據RWD原則[20]實施網頁具有挑戰性,我們之前的工作提出了一種回歸檢查技術,該技術比較了頁面的兩個模型并提出了任何差異[40]。專注于處理與檢查響應式網頁的問題正交的問題,其他先前的工作(例如,[17,18])沒有采用設計用于檢查多個窗口的Oracle。相比之下,本文的自動化方法利用隱式Oracle信息來檢測響應式布局故障。實驗表明它是有效的:在8個網頁中發現2個或更多的故障,它在一個網頁中發現了6個故障,在26個對象中檢測到總共33個不同的故障。
此文由南京大學軟件學院2016級本科生虞圣呈翻譯轉述。
讀
本文內容是筆者最近實現的 web 端彈幕組件—— Barrage UI(https://github.com/parksben/barrage) 的一個延伸。在閱讀本文的實例和相關代碼之前,不妨先瀏覽項目文檔,對組件的使用方式和相關接口進行了解。
各位童鞋如果經常上 B 站(bilibili.com) ,應該對 蒙版彈幕 這個概念并不陌生。
蒙版彈幕是由知名彈幕視頻網站 bilibili 于 2018 年中推出的一種彈幕渲染效果,可以有效減少彈幕文字對視頻主體信息的干擾。
關于 B 站蒙版彈幕的實現原理,其實網上已經有很多細致的討論和研究。個人總結了一下,大致要點如下:
客戶端方面,由于 B 站彈幕是基于 div+css 的實現,因而采用了 svg 格式來傳輸矢量蒙版(至少目前是這樣),通過 CSS 遮罩的方式實現渲染。
■乎上有一篇關于這個方案的討論,感興趣的童鞋可以移步 這里(https://www.zhihu.com/question/279446628) 進行了解。
Barrage UI(https://github.com/parksben/barrage) 是個人最近實現的一個前端彈幕組件,主要用于在前端頁面中掛載彈幕動畫。
組件提供了一系列的操作接口以方便用戶對彈幕的相關特性進行定制。你也可以在渲染層面對動畫中的每一幀圖像進行處理,比如:
下面是基于 Barrage UI 組件實現的蒙版彈幕效果:
編者注:圖片這么糊是微信公眾平臺的鍋。
由于文中不方便嵌入視頻,Demo 的實際效果請移步到 此處(https://parksben.github.io/masking-danmaku-demo) 查看。
下面我們來介紹如何實現上圖的動畫效果。
Demo 中使用了初音小姐姐跳舞的視頻。最主要的特點是除了人物外,視頻的背景是比較一致的純色。對于這種類型的圖像,我們可以使用 色鍵 的方式進行摳圖(生成“蒙版”)。
色度鍵控(https://zh.wikipedia.org/zh/%E8%89%B2%E9%94%AE),又稱色彩嵌空,是一種去背合成技術。Chroma 為純色之意,Key 則是抽離顏色之意。把被拍攝的人物或物體放置于綠幕的前面,并進行去背后,將其替換成其他的背景。此技術在電影、電視劇及游戲制作中被大量使用,色鍵也是虛擬攝影棚(Virtual studio)與視覺效果(Visual effects)當中的一個重要環節。
下圖是色鍵技術的一個示例:在綠幕前穿著藍色衣服的小姐姐,左圖為去背前,右圖為去背后的新背景。
在瀏覽器環境中,我們可以通過 canvas 畫布實時地繪制視頻的每一幀,并從畫布中讀取到圖像中每個像素的 RGBA 信息,檢測每個點的 R(red)、G(green)、B(blue) 值是否滿足要求,最終將需要扣除的像素的 A(alpha) 值置為 0,即可得到用于合成蒙版彈幕的蒙版圖像。
注意:
Barrage UI 組件的蒙版功能是基于 Canvas 2D API 的 CanvasRenderingContext2D.globalCompositeOperation 屬性實現的(使用了 source-in 的混合模式),因而只需將不需要的像素設置為透明(alpha=0)即可,并不需要改變圖像的 RGB 色值。
下面介紹此案例的代碼實現。
直接使用 yarn 或 npm 安裝此組件:
yarn add barrage-ui or npm install --save barrage-ui
準備一個 video 元素用于播放視頻,video 的父級元素用于掛載彈幕:
<div id="container">
<video id="video" src="videos/demo.mp4" controls>
</video>
</div>
根據視頻的實際尺寸(880×540)設置 #container 與 #video 的樣式:
html,
body {
font: 14px/18px Helvetica, Arial, 'Microsoft Yahei', Verdana, sans-serif;
width: 100%;
margin: 0;
padding: 0;
background: #eee;
overflow: hidden;
}
#container,
#video {
width: 880px;
height: 540px;
}
#container {
margin: 0 auto;
margin-top: 50vh;
margin-left: 50vw;
transform: translate(-50%, -50%);
background-color: #ddd;
}
import Barrage from 'barrage-ui';
import data from 'utils/mockData';
// 獲取父級容器
const container = document.getElementById('container');
// 創建彈幕實例
const barrage = new Barrage({
container: container,
});
// 重置畫布高度,避免彈幕遮擋視頻播放控件
barrage.canvas.height = container.clientHeight - 80;
// 裝填彈幕數據
barrage.setData(data);
其中,mockData 是用于生成隨機彈幕數據的方法。
關于彈幕數據的內容與格式,詳見 Barrage UI 項目文檔(https://github.com/parksben/barrage#%E8%A3%85%E5%A1%AB%E5%BC%B9%E5%B9%95)
// 獲取 video 元素
const video = document.getElementById('video');
// 新建一個畫布來實時繪制視頻(純繪圖,不用添加進頁面)
const vCanvas = document.createElement('canvas');
vCanvas.width = video.clientWidth;
vCanvas.height = video.clientHeight;const vContext = vCanvas.getContext('2d');
// 實時繪制視頻到畫布
barrage.afterRender = () => {
vContext.drawImage(video, 0, 0, vCanvas.width, vCanvas.height);
};
使用組件提供的渲染周期鉤子 .afterRender() 可以在彈幕動畫的每一幀圖像渲染后,將視頻圖像繪制到中間畫布 vCanvas 上。注意這里的 vCanvas 畫布主要用于實時地獲取視頻圖像,并不需要添加到頁面中。
// 渲染前讀取畫布 vCanvas 的數據,并處理為蒙版圖像
barrage.beforeRender = () => {
// 讀取圖像
const frame = vContext.getImageData(0, 0, vCanvas.width, vCanvas.height);
// 圖像總像素個數
const pxCount = frame.data.length / 4;
// 將 frame 構造成我們需要的蒙版圖像
for (let i = 0; i < pxCount; i++) {
// 這里不用 ES6 解構賦值的寫法,主要為了保證性能
// PS: 這里如果用解構賦值語法將導致大量新對象的創建,是個很耗時的過程
const r = frame.data[i * 4 + 0];
const g = frame.data[i * 4 + 1];
const b = frame.data[i * 4 + 2];
// 將黑色區域以外的內容設為透明
if (r > 15 || g > 15 || b > 15) {
frame.data[4 * i + 3] = 0;
}
}
// 設置蒙版
barrage.setMask(frame);
};
使用組件提供的渲染周期鉤子 .beforeRender() 可以在彈幕動畫的每一幀圖像渲染前計算出蒙版圖像。其中,用于更新蒙版的接口為 .setMask()。
最后,為了讓彈幕的行為與視頻播放的操作協同,還需要進行一些綁定的操作:
// 綁定播放事件
video.addEventListener( 'play',
( ) => {
barrage.play();
}, false);
// 綁定暫停事件
video.addEventListener( 'pause',
( ) => {
barrage.pause();
}, false);
// 切換播放進度
video.addEventListener( 'seeked',
( ) => {
barrage.goto(video.currentTime * 1000);
}, false);
這里分別用到 Brrage UI 組件的 .play() .pause .goto() 三個接口,分別用于播放、暫停和切換彈幕動畫的進度。需要注意的是,通過 video.currentTime 屬性獲取到的視頻播放進度是一個單位為 秒 的浮點數,需要轉換為 毫秒數 再傳給彈幕組件。
本文的案例已上傳 Github,感興趣的童鞋可以點擊 這里(https://github.com/parksben/masking-danmaku-demo) 查看源碼細節。
關于 Barrage UI 組件如果有什么建議和疑問,歡迎大家在項目中提 issue 給我,幫助我持續改進和迭代,更歡迎 star 和 PR。
作者:Parksben
來源:微信公眾號:創宇前端
出處:https://mp.weixin.qq.com/s/0HwQh5nUaK6Vi0EWbU8ENQ
*請認真填寫需求信息,我們會在24小時內與您取得聯系。