123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764 |
- <template>
- <div style="width: 100%; height: 100%; position: relative; overflow: hidden">
- <iframe
- :src="src"
- width="100%"
- scrolling="no"
- height="100%"
- style="position: relative; border: medium none;"
- ref="iframeRef"
- @load="onLoad"
- >
- </iframe>
- <div class="close-icon" @click="emits('close')" v-if="false">
- <CloseOutlined />
- </div>
- </div>
- </template>
- <script setup>
- import { CloseOutlined } from '@ant-design/icons-vue';
- import { message } from 'ant-design-vue';
- import { nextTick } from 'vue';
- const iframeRef = ref(null);
- const emits = defineEmits(['close', 'outline', 'search', 'load']);
- const searchList = ref([]);
- const pageContent = ref([]);
- const pageText = ref([]);
- const props = defineProps({
- src: String
- });
- const type = ref('pdf');
- watch(
- () => props.src,
- (v) => {
- if (v.endsWith('.pdf')) {
- type.value = 'pdf';
- } else if (v.endsWith('.txt')) {
- type.value = 'txt';
- }
- },
- { immediate: true }
- );
- const onLoad = () => {
- const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- //待定
- // var toolbar = doc.getElementsByClassName('toolbar');
- // if (toolbar && toolbar.length > 0) {
- // toolbar[0].style.display = 'none';
- // }
- setTimeout(() => {
- const script = doc.createElement('script');
- script.textContent = `
- window.addEventListener('message', (event) => {
- const message = event.data;
- if (message.action === 'locateElement') {
- const element= document.getElementById('hightlight-'+message.index);
- debugger;
- if(element!=null&&element.offsetParent){
- setTimeout(() => {
- element.scrollIntoView({ behavior: 'smooth', block: 'center' })
- }, 10);
- }
- }
- if (message.action === 'mlLocateElement') {
- const elements= document.getElementsByClassName(message.className);
- if(elements!=null){
- setTimeout(() => {
- elements[message.index].scrollIntoView({ behavior: 'smooth', block: 'center' })
- }, 50);
- }
- }
- });`;
- doc.body.appendChild(script);
- const style = doc.createElement('style');
- style.textContent = `
- #outerContainer{
- overflow: hidden;
- }
- #page-container{
- overflow: hidden;
- }
- #viewerContainer{
- overflow: hidden;
- }
- .canvasWrapper {
- position: relative;
- /* display: flex;
- justify-content: center; */
- }
- .textLayer {
- text-align: initial;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- opacity: 1;
- line-height: 1;
- text-size-adjust: none;
- forced-color-adjust: none;
- }
- .textLayer span,
- .textLayer br {
- color: transparent;
- position: absolute;
- white-space: pre;
- cursor: text;
- transform-origin: 0% 0%;
- }
- .highlight-layer {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- opacity: 0.15;
- overflow: hidden;
- }
- .flash {
- animation: flashing 0.6s;
- animation-iteration-count: 2;
- }
- @keyframes flashing {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 0.15;
- }
- }
- .highlight {
- background-color: #0076fa;
- position: absolute;
- }
- `;
- doc.head.appendChild(style);
- }, 400);
- setTimeout(() => {
- var el = doc.getElementsByClassName('page');
- // var singleHeight = 1263;
- var singleHeight = 1000;
- if (iframeRef.value.height != singleHeight * el.length) {
- iframeRef.value.height = singleHeight * el.length;
- }
- setTimeout(() => {
- pageText.value = [];
- pageContent.value = [];
- //获取每页数据
- var textDivs = doc.getElementsByClassName('textLayer');
- if (textDivs) {
- for (var i = 0; i < textDivs.length; i++) {
- var item = { page: i + 1, txt: textDivs[i].innerText };
- var items = [];
- for (var j = 0; j < textDivs[i].children.length; j++) {
- var citem = {
- str: textDivs[i].children[j].innerText,
- width: textDivs[i].children[j].getBoundingClientRect().width,
- top: textDivs[i].children[j].getBoundingClientRect().top,
- height: textDivs[i].children[j].getBoundingClientRect().height
- };
- items.push(citem);
- }
- pageContent.value.push({ page: i + 1, items: items });
- pageText.value.push(item);
- }
- }
- }, 1000);
- }, 1000);
- };
- const src = computed(() => {
- // return `/lib/pdfjs/web/viewer.html?file=${props.src}&t=` + new Date().getTime();
- return `/aisearch/lib/pdfjs/web/viewer.html?file=${props.src}`;
- // return `/aisearch/lib/pdfjs/web/viewer.html?file=${props.src}&t=` + new Date().getTime();
- // return `/lib/pdfjs/web/viewer.html?file=http://121.40.148.47:8530/doc/knowledge_base/download_doc/国土资源部 国家发展和改革委员会+财政部+住房和城乡建设部农业部+中国人民银行+国家林业局+中国银行业监督管理委员会关于扩大国有土地有偿使用范围的意见%28279-283%29.pdf`
- });
- const scrollTo = (item) => {
- nextTick(() => {
- setTimeout(() => {
- if (item.top) {
- locateElement(item.cs, item.index, 1);
- } else {
- //搜索
- locateElement(item.cs, item.index);
- //改变选中颜色 其他黄色
- searchList.value.forEach((citem, cindex) => {
- const iframeDocument =
- iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- const highlightedSpan = iframeDocument.getElementById('hightlight-' + cindex);
- if (cindex == item.index) {
- highlightedSpan.parentNode.innerHTML = highlightedSpan.parentNode.innerHTML.replace(
- 'lightblue',
- 'yellow'
- );
- } else {
- highlightedSpan.parentNode.innerHTML = highlightedSpan.parentNode.innerHTML.replace(
- 'yellow',
- 'lightblue'
- );
- }
- });
- }
- }, 10);
- });
- };
- const locateElement = (className, index, type) => {
- const message = {
- action: type ? 'mlLocateElement' : 'locateElement',
- className: className,
- index: index
- };
- iframeRef.value.contentWindow.postMessage(message, '*');
- };
- const gotoElement = (element) => {
- nextTick(() => {
- element.scrollIntoView({ behavior: 'smooth', block: 'center' });
- });
- };
- const highlightText = (str) => {
- setTimeout(() => {
- removeHighlight();
- heightSingle(str);
- }, 100);
- };
- const goLocation = (txt, flag) => {
- removeHighlight();
- searchList.value = [];
- heightSingle(txt, flag);
- };
- //ai 问答
- const goSourceLocation = (content, type) => {
- //获取页面
- removeSourceHightLight();
- const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- var pageNumber = 0;
- if (content.indexOf('-') > -1 && content.indexOf('-') < 3) {
- content = content.substring(content.indexOf('-') + 1);
- }
- var text = content;
- var ptText = text.replaceAll('\n', '');
- ptText = ptText.replaceAll(' ', '');
- console.log(ptText);
- pageText.value.forEach((item) => {
- item.txt = item.txt.replaceAll('\n', '');
- item.txt = item.txt.replaceAll(' ', '');
- console.log('==页=' + item.txt);
- console.log('==t=' + ptText);
- if (ptText.length > 30) {
- if (item.txt.indexOf(ptText.substring(30, 48)) > -1) {
- pageNumber = item.page;
- }
- } else {
- if (item.txt.indexOf(ptText.substring(0, 18)) > -1) {
- pageNumber = item.page;
- }
- }
- });
- text = text.replaceAll('\n', '');
- text = text.replaceAll(' ', '');
- if (text.endsWith('。') || text.endsWith(',') || text.endsWith('、')) {
- text = text.substring(0, text.length - 1);
- }
- if (pageNumber == 0) {
- message.info('未匹配到相关内容!');
- return;
- }
- //'left: 133.17px; top: 152.408px; font-size: 31.3597px; font-family: sans-serif; transform: scaleX(1.01878);'
- var arr = doc.getElementsByClassName('textLayer')[pageNumber - 1].getElementsByTagName('span');
- if (arr) {
- var hightList = [];
- var startIndex = 0;
- var endIndex = 0;
- var thirdIndex = 0;
- startIndex = getStartIndex(arr, text, text.length);
- endIndex = getEndIndex(arr, text, text.length, startIndex);
- if (startIndex && endIndex && endIndex < startIndex) {
- endIndex = startIndex + text.length;
- }
- var currentPageIndex = 0;
- var nextArr = [];
- var thirdArr = [];
- if (endIndex == undefined) {
- currentPageIndex = arr.length;
- if (doc.getElementsByClassName('textLayer')[pageNumber]) {
- nextArr = doc.getElementsByClassName('textLayer')[pageNumber].getElementsByTagName('span');
- endIndex = getEndIndex(nextArr, text, text.length, startIndex);
- if (endIndex == undefined && doc.getElementsByClassName('textLayer')[pageNumber + 1]) {
- thirdArr = doc
- .getElementsByClassName('textLayer')
- [pageNumber + 1].getElementsByTagName('span');
- thirdIndex = getEndIndex(thirdArr, text, text.length, 0);
- }
- } else {
- endIndex = undefined;
- }
- } else {
- currentPageIndex = endIndex;
- if (type == 1) {
- if (endIndex && startIndex && endIndex > startIndex + content.length) {
- currentPageIndex = startIndex + content.length;
- }
- //中间标点匹配问题
- // var len = startIndex + content.length + 5;
- // if (startIndex && endIndex && endIndex > (startIndex + content.length + 5)) {
- // currentPageIndex = len;
- // }
- }
- }
- if (!startIndex) {
- startIndex = 0;
- }
- for (var i = startIndex; i < currentPageIndex + 1; i++) {
- var element = arr[i];
- if (!element) {
- break;
- }
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- var s = spanText.replaceAll(' ', '');
- var d = spanText.endsWith('。') ? spanText.replaceAll('。', '') : spanText;
- console.log(spanText);
- if (
- text.includes(spanText) ||
- (s && text.includes(s)) ||
- (d && text.includes(d)) ||
- (spanText.substring(2) && text.includes(spanText.substring(2)))
- ) {
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- var leftLabel = cssText.split('left: ')[1].split(';')[0];
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber) {
- currentItems = item.items;
- }
- });
- for (var m = 0; m < currentItems.length; m++) {
- // citem.str=citem.str.replaceAll(' ','');
- var citem = currentItems[m];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = leftLabel;
- hItem.top = topLabel;
- hightList.push(hItem);
- if (hightList.length == 1) {
- gotoElement(element);
- }
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- }
- if (type == 1 && hightList.length == 0) {
- message.info('未匹配到相关内容!');
- }
- // if (type == 2) {
- var nextHightList = [];
- //跨页 目前只考虑跨1页
- if (endIndex != undefined) {
- for (var i = 0; i < nextArr.length; i++) {
- var element = nextArr[i];
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- console.log(spanText);
- if (text.includes(spanText)) {
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- var leftLabel = cssText.split('left: ')[1].split(';')[0];
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber + 1) {
- currentItems = item.items;
- }
- });
- for (var l = 0; l < currentItems.length; l++) {
- var citem = currentItems[l];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = leftLabel;
- hItem.top = topLabel;
- nextHightList.push(hItem);
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- if (text.endsWith(spanText)) {
- break;
- }
- }
- } else {
- //跨页 目前只考虑跨2页
- for (var i = 0; i < nextArr.length; i++) {
- var element = nextArr[i];
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- console.log(spanText);
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- var leftLabel = cssText.split('left: ')[1].split(';')[0];
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber + 1) {
- currentItems = item.items;
- }
- });
- for (var l = 0; l < currentItems.length; l++) {
- var citem = currentItems[l];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = leftLabel;
- hItem.top = topLabel;
- nextHightList.push(hItem);
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- var thirdHightList = [];
- if (thirdIndex != 0 && thirdIndex != undefined) {
- for (var z = 0; z < thirdIndex + 1; z++) {
- var element = thirdArr[z];
- var hItem = {};
- var cssText = element.style.cssText;
- var spanText = element.innerText;
- console.log(spanText);
- if (text.includes(spanText)) {
- var topLabel = cssText.split('top: ')[1].split(';')[0];
- var leftLabel = cssText.split('left: ')[1].split(';')[0];
- var currentItems = [];
- pageContent.value.forEach((item) => {
- if (item.page == pageNumber + 2) {
- currentItems = item.items;
- }
- });
- for (var l = 0; l < currentItems.length; l++) {
- var citem = currentItems[l];
- if (citem.str == spanText) {
- hItem.width = parseFloat(citem.width);
- hItem.height = parseFloat(citem.height);
- hItem.text = spanText;
- hItem.left = leftLabel;
- hItem.top = topLabel;
- thirdHightList.push(hItem);
- if (text.endsWith(citem.str)) {
- break;
- }
- }
- }
- }
- if (text.endsWith(spanText)) {
- break;
- }
- }
- }
- }
- //渲染
- var canvas = doc.getElementsByClassName(`page`)[pageNumber - 1];
- const hightDiv = document.createElement('div');
- hightDiv.className = 'highlight-layer flash';
- hightList.forEach((it) => {
- const hDiv = document.createElement('div');
- hDiv.className = 'highlight';
- // 设置内联样式
- hDiv.style.width = it.width + 'px';
- hDiv.style.height = it.height * 1.4 + 'px';
- hDiv.style.left = it.left;
- hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
- hightDiv.appendChild(hDiv);
- });
- canvas.appendChild(hightDiv);
- if (nextHightList && nextHightList.length > 0) {
- //渲染
- var nextCanvas = doc.getElementsByClassName(`page`)[pageNumber];
- const hightDiv = document.createElement('div');
- hightDiv.className = 'highlight-layer flash';
- nextHightList.forEach((it) => {
- const hDiv = document.createElement('div');
- hDiv.className = 'highlight';
- // 设置内联样式
- hDiv.style.width = it.width + 'px';
- hDiv.style.height = it.height * 1.4 + 'px';
- hDiv.style.left = it.left;
- hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
- hightDiv.appendChild(hDiv);
- });
- nextCanvas.appendChild(hightDiv);
- }
- if (thirdHightList && thirdHightList.length > 0) {
- //渲染
- var thirdCanvas = doc.getElementsByClassName(`page`)[pageNumber + 1];
- const hightDiv = document.createElement('div');
- hightDiv.className = 'highlight-layer flash';
- thirdHightList.forEach((it) => {
- const hDiv = document.createElement('div');
- hDiv.className = 'highlight';
- // 设置内联样式
- hDiv.style.width = it.width + 'px';
- hDiv.style.height = it.height * 1.4 + 'px';
- hDiv.style.left = it.left;
- hDiv.style.top = parseFloat(it.top.split('%')[0]) - 0.5 + '%';
- hightDiv.appendChild(hDiv);
- });
- thirdCanvas.appendChild(hightDiv);
- }
- // }
- }
- };
- //交集
- function commonContent(str1, str2) {
- let result = '';
- for (let i = 0; i < str1.length; i++) {
- if (str2.includes(str1[i])) {
- result += str1[i];
- }
- }
- return result;
- }
- const getStartIndex = (arr, text, total) => {
- var indexs = [];
- for (var i = 0; i < arr.length; i++) {
- var element = arr[i];
- var spanText = element.innerText;
- console.log('===' + spanText);
- if (
- (spanText && text.startsWith(spanText)) ||
- (spanText && spanText.substring(2) && spanText && text.startsWith(spanText.substring(2))) ||
- (spanText.indexOf('。') > -1 &&
- spanText.split('。')[1] &&
- text.startsWith(spanText.split('。')[1])) ||
- (spanText.endsWith('。') &&
- spanText.split('。')[0] &&
- text.startsWith(spanText.split('。')[0]))
- ) {
- indexs.push(i);
- // return i;
- }
- }
- if (indexs.length == 0) {
- return 0;
- } else if (indexs.length == 2) {
- console.log(JSON.stringify(indexs));
- return indexs[0] == 0 ? indexs[1] : indexs[0];
- } else {
- return indexs[0];
- }
- //todo 有问题
- // if (count == arr.length) {
- // start.value = start.value + 1;
- // text = text.substring(start.value, total);
- // getStartIndex(arr, text, total);
- // }
- };
- const getEndIndex = (arr, text, total, startIndex) => {
- for (var i = 0; i < arr.length; i++) {
- var element = arr[i];
- var spanText = element.innerText;
- var e = '';
- if (spanText.endsWith('。')) {
- spanText = spanText.substring(0, spanText.lastIndexOf('。'));
- }
- if (spanText.indexOf('。') > -1) {
- e = spanText.substring(0, spanText.lastIndexOf('。'));
- }
- console.log('===' + spanText);
- if (
- (spanText && text.endsWith(spanText) && spanText.length > 0) ||
- (spanText && spanText.indexOf(text) > -1) ||
- (e && text.endsWith(e))
- ) {
- return i;
- }
- }
- };
- const heightSingle = (str, flag) => {
- const iframeDocument = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- const searchText = str;
- var highlightColor = 'lightblue';
- // 使用DOM操作在iframe中搜索并高亮文本
- const textNodes = iframeDocument.evaluate(
- ".//text()[contains(., '" + searchText + "')]",
- iframeDocument,
- null,
- XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE,
- null
- );
- var firstFlag = false;
- for (let i = 0, length = textNodes.snapshotLength; i < length; i++) {
- const content = textNodes.snapshotItem(i);
- const textNode = content.textContent;
- const regex = new RegExp(searchText, 'gi');
- var newContent = textNode.replace(regex, (match) => {
- if (match == searchText) {
- if (!firstFlag) {
- firstFlag = true;
- highlightColor = 'yellow';
- } else {
- highlightColor = 'lightblue';
- }
- return `<span id='hightlight-${searchList.value.length}' style="background-color: ${highlightColor};color: #333;margin-top:-3px;border-radius: 3px;">${match}</span>`;
- }
- });
- if (content.parentNode) {
- var n = content.parentNode;
- console.log('====' + n.offsetTop);
- newContent = '<' + newContent.split('<')[1] + '</span>';
- var h = content.parentNode.innerHTML;
- if (!flag) {
- content.parentNode.innerHTML = h.replace(searchText, newContent);
- }
- if (n) {
- searchList.value.push({
- cs: n.parentNode.className,
- index: searchList.value.length,
- n: n.parentNode
- });
- if (searchList.value.length == 1) {
- // if (!flag) {
- // locateElement(searchList.value[0].cs, 0);
- // }
- gotoElement(n);
- }
- //大纲只定位一次
- if (flag) {
- break;
- }
- }
- }
- if (!flag) {
- if (i == textNodes.snapshotLength - 1) {
- //查询结束
- emits('search', searchList.value);
- }
- }
- }
- };
- const removeSourceHightLight = () => {
- //获取页面 ai定位移除
- const doc = iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- const arr = doc.getElementsByClassName('highlight-layer');
- const l = arr.length;
- for (let i = l - 1; i >= 0; i--) {
- if (arr[i] != null) {
- arr[i].parentNode.removeChild(arr[i]);
- }
- }
- };
- const removeHighlight = () => {
- removeSourceHightLight();
- if (searchList.value) {
- searchList.value.forEach((item, index) => {
- const iframeDocument =
- iframeRef.value.contentDocument || iframeRef.value.contentWindow.document;
- const highlightedSpan = iframeDocument.getElementById('hightlight-' + index);
- if (highlightedSpan != null) {
- const span = highlightedSpan;
- console.log(span.innerHTML);
- setTimeout(() => {
- span.parentNode.replaceChild(document.createTextNode(span.textContent), span);
- }, 10);
- }
- });
- }
- searchList.value = [];
- emits('search', searchList.value);
- };
- defineExpose({ highlightText, removeHighlight, scrollTo, goLocation, goSourceLocation, location });
- </script>
- <style scoped lang="scss">
- .close-icon {
- position: absolute;
- top: 8px;
- right: 12px;
- cursor: pointer;
- }
- </style>
- <style>
- /* 不能用scoped,因为动态创建的元素不会被编译带上hash */
- /* 不加上这些css修饰,元素会错位,样式来自vue-pdf-embed这个库 */
- .canvasWrapper {
- position: relative;
- /* display: flex;
- justify-content: center; */
- }
- .page {
- position: relative;
- box-shadow: 0 4px 10px 0 rgba(0, 0, 0, 0.08);
- margin: 30px auto;
- }
- .textLayer {
- text-align: initial;
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- overflow: hidden;
- opacity: 0.2;
- line-height: 1;
- text-size-adjust: none;
- forced-color-adjust: none;
- }
- .textLayer span,
- .textLayer br {
- color: transparent;
- position: absolute;
- white-space: pre;
- cursor: text;
- transform-origin: 0% 0%;
- }
- .highlight-layer {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- pointer-events: none;
- opacity: 0.15;
- overflow: hidden;
- }
- .flash {
- animation: flashing 0.6s;
- animation-iteration-count: 2;
- }
- @keyframes flashing {
- 0% {
- opacity: 0;
- }
- 100% {
- opacity: 0.15;
- }
- }
- .highlight {
- background-color: #0076fa;
- position: absolute;
- }
- </style>
|