Использовали ли вы эти API, обычно используемые в расширенном интерфейсе?

внешний интерфейс JavaScript
Использовали ли вы эти API, обычно используемые в расширенном интерфейсе?

Эта статья включена в githubGitHub.com/Майкл-Ли Чжиган…

MutationObserver

MutationObserver — это интерфейс, который может отслеживать изменения в структуре DOM. MutationObserver будет уведомлен о любых изменениях в дереве объектов DOM.

API

MutationObserver — это конструктор, который принимает параметр обратного вызова, функцию обратного вызова, используемую для обработки изменений узлов, и возвращает два параметра:

  • мутации: список записей об изменении узла(sequence<MutationRecord>)
  • наблюдатель: создает объект MutationObserver.

Объект MutationObserver имеет три следующих метода:

  • наблюдать: установить цель наблюдения, принять два параметра, цель: цель наблюдения, параметры: установить параметры наблюдения через элементы объекта
  • отключить: запретить наблюдателю наблюдать какие-либо изменения
  • takeRecords: очистить очередь записей и вернуть содержимое
//选择一个需要观察的节点
var targetNode = document.getElementById('root')

// 设置observer的配置选项
var config = { attributes: true, childList: true, subtree: true }

// 当节点发生变化时的需要执行的函数
var callback = function (mutationsList, observer) {
  for (var mutation of mutationsList) {
    if (mutation.type == 'childList') {
      console.log('A child node has been added or removed.')
    } else if (mutation.type == 'attributes') {
      console.log('The ' + mutation.attributeName + ' attribute was modified.')
    }
  }
}

// 创建一个observer示例与回调函数相关联
var observer = new MutationObserver(callback)

//使用配置文件对目标节点进行观测
observer.observe(targetNode, config)

// 停止观测
observer.disconnect()

Параметр options в методе наблюдения имеет следующие параметры:

  • childList: установите значение true, что означает наблюдение за изменениями целевого дочернего узла, такими как добавление или удаление целевого дочернего узла, исключая модификацию дочернего узла и изменения потомков дочернего узла.
  • атрибуты: установлено значение true, что указывает на изменения в атрибутах цели наблюдения
  • characterData: установлено значение true, что указывает на изменение данных цели наблюдения.
  • поддерево: установлено значение true, цель и ее потомки будут отслеживаться на предмет изменений
  • attributeOldValue: Если атрибут имеет значение true или опущен, это эквивалентно присвоению ему значения true, что указывает на то, что значение целевого атрибута перед изменением необходимо записать.Если атрибут attributeOldValue установлен, настройку атрибутов можно опустить.
  • characterDataOldValue: если characterData имеет значение true или опущено, это эквивалентно установке значения true, что указывает на необходимость записи целевых данных перед изменением.Если установлено значение characterDataOldValue, параметр characterData можно опустить.
  • attributeFilter: если необходимо отслеживать не все изменения атрибутов, а для атрибутов задано значение true или они игнорируются, то задается список локальных имен атрибутов для наблюдения (пространство имен не требуется).

Функции

MutationObserver имеет следующие характеристики:

  • Он ожидает завершения всех задач сценария перед запуском, то есть асинхронно.
  • Он инкапсулирует запись изменения DOM в массив для обработки, а не обрабатывает изменения DOM одно за другим.
  • Он может наблюдать за всеми изменениями, которые происходят в узлах DOM, а также может наблюдать за определенным типом изменений.

Событие MutationObserver запускается при изменении DOM. Однако существенно отличается от события: событие срабатывает синхронно, то есть соответствующее событие срабатывает сразу при изменении DOM, MutationObserver срабатывает асинхронно, после изменения DOM не срабатывает сразу , но будет ждать, пока текущий Fired не будет завершен после завершения всех манипуляций с DOM.

Например, если в документ непрерывно вставляется 1000 абзацев (элементов p), будет непрерывно запускаться 1000 событий вставки, и будет выполняться функция обратного вызова каждого события, что, вероятно, приведет к зависанию браузера; в то время как MutationObserver полностью отличается, только он будет запущен после того, как будет вставлено 1000 абзацев, и он будет запущен только один раз, что снижает частые изменения DOM и значительно повышает производительность.

IntersectionObserver

При разработке веб-страниц часто необходимо знать, попал ли элемент в «окно просмотра», то есть может ли его увидеть пользователь.

Традиционный метод реализации заключается в вызове метода getBoundingClientRect() целевого элемента после прослушивания события прокрутки, чтобы получить координаты, соответствующие верхнему левому углу области просмотра, а затем определить, находится ли он в пределах области просмотра. Недостатком этого метода является то, что из-за интенсивного возникновения событий прокрутки объем вычислений очень велик, что легко может вызвать проблемы с производительностью.

В настоящее время существует новый API IntersectionObserver, который автоматически «наблюдает», виден ли элемент, который уже поддерживается Chrome 51+. Поскольку сущность visible заключается в том, что целевой элемент и область просмотра создают пересечение, этот API называется «средством просмотра пересечений».

API

IntersectionObserver — это конструктор, изначально предоставленный браузером и принимающий два параметра: callback — это функция обратного вызова при изменении видимости, а option — объект конфигурации (этот параметр является необязательным).

var io = new IntersectionObserver(callback, option)

// 开始观察
io.observe(document.getElementById('example'))

// 停止观察
io.unobserve(element)

// 关闭观察器
io.disconnect()

Если вы хотите наблюдать за несколькими узлами, вам нужно вызвать этот метод несколько раз.

io.observe(elementA)
io.observe(elementB)

Когда видимость целевого элемента изменяется, вызывается функция обратного вызова наблюдателя. Обратный вызов обычно запускается дважды. Один раз, когда целевой элемент только что вошел в область просмотра (начиная с видимого), и один раз, когда он полностью покинул область просмотра (начало с невидимого).

var io = new IntersectionObserver((entries) => {
  console.log(entries)
})

Аргументы (записи) функции обратного вызова представляют собой массив, каждый элемент которого является объектом IntersectionObserverEntry. Например, если одновременно изменится видимость двух наблюдаемых объектов, массив элементов будет состоять из двух элементов.

  • время: время, когда изменилась видимость, это высокоточная временная метка в миллисекундах.
  • цель: целевой элемент, за которым нужно наблюдать, является объектом узла DOM.
  • isIntersecting: видна ли цель
  • rootBounds: информация о прямоугольной области корневого элемента, возвращаемое значение метода getBoundingClientRect() или null, если корневого элемента нет (то есть прокрутка непосредственно относительно окна просмотра)
  • boundingClientRect: информация о прямоугольной области целевого элемента
  • пересечениеRect: Информация о области пересечения целевого элемента и области просмотра (или корневого элемента)
  • пересечениеRatio: видимое отношение целевого элемента, то есть отношение пересеченияRect к boundingClientRect, 1, когда он полностью виден, и меньше или равно 0, когда он полностью невидим

Например

<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>Document</title>
    <style>
      #div1 {
        position: sticky;
        top: 0;
        height: 50px;
        line-height: 50px;
        text-align: center;
        background: black;
        color: #ffffff;
        font-size: 18px;
      }
    </style>
  </head>

  <body>
    <div id="div1">首页</div>
    <div style="height: 1000px;"></div>
    <div id="div2" style="height: 100px; background: red;"></div>
    <script>
      var div2 = document.getElementById('div2')
      let observer = new IntersectionObserver(
        function (entries) {
          entries.forEach(function (element, index) {
            console.log(element)
            if (element.isIntersecting) {
              div1.innerText = '我出来了'
            } else {
              div1.innerText = '首页'
            }
          })
        },
        {
          root: null,
          threshold: [0, 1]
        }
      )

      observer.observe(div2)
    </script>
  </body>
</html>

По сравнению с getBoundingClientRect у него есть то преимущество, что он не вызывает перерисовки перерисовки. Совместимость следующая

IntersectionObserver.png

Ленивая загрузка изображения

Принцип ленивой загрузки изображения в основном реализуется основной логикой оценки того, достигло ли текущее изображение видимой области. Это экономит пропускную способность и повышает производительность веб-страницы. Традиционный прорыв ленивой загрузки достигается за счет прослушивания события прокрутки, но событие прокрутки будет срабатывать много раз за короткий промежуток времени, что серьезно влияет на производительность страницы. Чтобы улучшить производительность страницы, мы можем использовать IntersectionObserver для ленивой загрузки изображений.

const imgs = document.querySelectorAll('img[data-src]')
const config = {
  rootMargin: '0px',
  threshold: 0
}
let observer = new IntersectionObserver((entries, self) => {
  entries.forEach((entry) => {
    if (entry.isIntersecting) {
      let img = entry.target
      let src = img.dataset.src
      if (src) {
        img.src = src
        img.removeAttribute('data-src')
      }
      // 解除观察
      self.unobserve(entry.target)
    }
  })
}, config)

imgs.forEach((image) => {
  observer.observe(image)
})

бесконечная прокрутка

Реализация бесконечной прокрутки также проста.

var intersectionObserver = new IntersectionObserver(function (entries) {
  // 如果不可见,就返回
  if (entries[0].intersectionRatio <= 0) return
  loadItems(10)
  console.log('Loaded new items')
})

// 开始观察
intersectionObserver.observe(document.querySelector('.scrollerFooter'))

getComputedStyle()

Стиль DOM2 вdocument.defaultViewДобавлен метод getComputedStyle(), который возвращаетCSSStyleDeclarationОбъект (того же типа, что и свойство стиля), содержащий вычисленный стиль для элемента.

API

document.defaultView.getComputedStyle(element[,pseudo-element])
// or
window.getComputedStyle(element[,pseudo-element])

Этот метод принимает два параметра: элемент, из которого нужно получить вычисляемый стиль, и строку псевдоэлемента (например, ":after"). Если вам не нужно запрашивать псевдоэлементы, второму параметру можно передать значение null.

<!DOCTYPE html>
<html>
  <head>
    <style type="text/css">
      #myDiv {
        background-color: blue;
        width: 100px;
        height: 200px;
      }
    </style>
  </head>
  <body>
    <div id="myDiv" style="background-color: red; border: 1px solid black"></div>
  </body>
  <script>
    function getStyleByAttr(obj, name) {
      return window.getComputedStyle ? window.getComputedStyle(obj, null)[name] : obj.currentStyle[name]
    }
    let node = document.getElementById('myDiv')
    console.log(getStyleByAttr(node, 'backgroundColor'))
    console.log(getStyleByAttr(node, 'width'))
    console.log(getStyleByAttr(node, 'height'))
    console.log(getStyleByAttr(node, 'border'))
  </script>
</html>

Сходства и различия со стилем

Сходство между getComputedStyle и element.style заключается в том, что оба возвращают объекты CSSStyleDeclaration. Разница в следующем:

  • element.style считывает только встроенный стиль элемента, то есть стиль, записанный в атрибуте стиля элемента, а стиль, считываемый функцией getComputedStyle, является окончательным стилем, включая встроенные стили, встроенные стили и внешние стили.
  • element.style поддерживает как чтение, так и запись, мы можем переписать стиль элемента через element.style. А getComputedStyle поддерживает только чтение, но не запись. Мы можем прочитать стиль с помощью getComputedStyle и изменить его с помощью element.style.

getBoundingClientRect

Метод getBoundingClientRect() возвращает размер элемента и его позицию относительно окна просмотра.

API

let DOMRect = object.getBoundingClientRect()

Его возвращаемое значение — это объект DOMRect, представляющий собой набор прямоугольников, возвращаемых методом getClientRects() элемента, который представляет собой размер границы элемента в CSS. Возвращаемый результат представляет собой наименьший прямоугольник, содержащий полный элемент, и имеет доступные только для чтения свойства в пикселях слева, сверху, справа, снизу, x, y, ширины и высоты, которые описывают всю границу. Свойства, отличные от ширины и высоты, рассчитываются относительно левого верхнего угла окна просмотра.

getBoundingClientRect.png

Сценарии применения

1. Получите расстояние элемента dom относительно левого верхнего угла веб-страницы.

Предыдущий способ записи состоит в том, чтобы найти элемент через offsetParent, чтобы найти родительский элемент, пока он не рекурсивно вернется к телу элемента верхнего уровня или html.

// 获取dom元素相对于网页左上角定位的距离
function offset(el) {
  var top = 0
  var left = 0
  do {
    top += el.offsetTop
    left += el.offsetLeft
  } while ((el = el.offsetParent)) // 存在兼容性问题,需要兼容
  return {
    top: top,
    left: left
  }
}

var odiv = document.getElementsByClassName('markdown-body')
offset(a[0]) // {top: 271, left: 136}

Теперь по апи getBoundingClientRect можно написать так:

var positionX = this.getBoundingClientRect().left + document.documentElement.scrollLeft
var positionY = this.getBoundingClientRect().top + document.documentElement.scrollTop

2. Определяем, находится ли элемент в видимой области

function isElView(el) {
  var top = el.getBoundingClientRect().top // 元素顶端到可见区域顶端的距离
  var bottom = el.getBoundingClientRect().bottom // 元素底部端到可见区域顶端的距离
  var se = document.documentElement.clientHeight // 浏览器可见区域高度。
  if (top < se && bottom > 0) {
    return true
  } else if (top >= se || bottom <= 0) {
    // 不可见
  }
  return false
}

requestAnimationFrame

window.requestAnimationFrame() сообщает браузеру, что вы хотите выполнить анимацию, и просит браузер вызвать указанную функцию обратного вызова, чтобы обновить анимацию перед следующей перерисовкой.

API

Этот метод должен передать функцию обратного вызова в качестве параметра, функция обратного вызова будет выполнена до следующей перерисовки браузера.

window.requestAnimationFrame(callback)

Совместимость

window._requestAnimationFrame = (function () {
  return (
    window.requestAnimationFrame ||
    window.webkitRequestAnimationFrame ||
    window.mozRequestAnimationFrame ||
    function (callback) {
      window.setTimeout(callback, 1000 / 60)
    }
  )
})()

конец анимации

var globalID
function animate() {
  // done(); 一直运行
  globalID = requestAnimationFrame(animate) // Do something animate
}
globalID = requestAnimationFrame(animate) //开始
cancelAnimationFrame(globalID) //结束

По сравнению с setTimeout самым большим преимуществом requestAnimationFrame является то, что система определяет время выполнения функции обратного вызова. В частности, если частота обновления экрана составляет 60 Гц, то функция обратного вызова выполняется каждые 16,7 мс.Если частота обновления составляет 75 Гц, то временной интервал становится равным 1000/75 = 13,3 мс, другими словами, requestAnimationFrame Темп следует за темпом. обновления системы. Это может гарантировать, что функция обратного вызова будет выполняться только один раз в каждом интервале обновления экрана, так что это не приведет к потере кадров или зависанию анимации. Вызов этого API прост:

var progress = 0
//回调函数
function render() {
  progress += 1 //修改图像的位置
  if (progress < 100) {
    //在动画没有结束前,递归渲染
    window.requestAnimationFrame(render)
  }
}
//第一帧渲染
window.requestAnimationFrame(render)

преимущество:

  • Энергосбережение ЦП: для анимаций, реализованных с помощью setTimeout, когда страница скрыта или свернута, setTimeout по-прежнему выполняет задачи анимации в фоновом режиме.Поскольку страница в это время невидима или недоступна, бессмысленно обновлять анимацию, которая является завершенной. пустая трата ресурсов процессора. RequestAnimationFrame совершенно другой. Когда обработка страницы не активирована, задача обновления экрана страницы также будет приостановлена ​​системой, поэтому requestAnimationFrame, который следует за системой, также прекратит рендеринг. Когда страница активирована, анимация будет начать с последнего раза.Выполнение продолжается с того места, где оно было остановлено, что эффективно снижает нагрузку на ЦП.

  • Дросселирование функции: в высокочастотных событиях (изменение размера, прокрутка и т. д.), чтобы предотвратить выполнение нескольких функций в течение интервала обновления, используйте requestAnimationFrame, чтобы гарантировать, что функция выполняется только один раз в каждом интервале обновления, что может обеспечить плавность. также может лучше сэкономить накладные расходы на выполнение функции. Для функции нет смысла выполнять несколько раз в течение интервала обновления, потому что дисплей обновляется каждые 16,7 мс, и многократные отрисовки не будут отображаться на экране.

Сценарии применения

1. Слушайте функцию прокрутки

Функция слушателя события прокрутки страницы (scroll) очень подходит для использования этого API для задержки следующего повторного рендеринга.

$(window).on('scroll', function () {
  window.requestAnimationFrame(scrollHandler)
})

Плавная прокрутка вверх страницы

const scrollToTop = () => {
  const c = document.documentElement.scrollTop || document.body.scrollTop
  if (c > 0) {
    window.requestAnimationFrame(scrollToTop)
    window.scrollTo(0, c - c / 8)
  }
}

scrollToTop()

2. Большой объем рендеринга данных

Например, рендеринг 100 000 фрагментов данных в основном включает следующие методы:

(1) Использование таймера

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  let pageCount = Math.min(curTotal, once)
  setTimeout(() => {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
      ul.appendChild(li)
    }
    loop(curTotal - pageCount, curIndex + pageCount)
  }, 0)
}
loop(total, index)

(2) Используйте requestAnimationFrame

//需要插入的容器
let ul = document.getElementById('container')
// 插入十万条数据
let total = 100000
// 一次插入 20 条
let once = 20
//总页数
let page = total / once
//每条记录的索引
let index = 0
//循环加载数据
function loop(curTotal, curIndex) {
  if (curTotal <= 0) {
    return false
  }
  //每页多少条
  let pageCount = Math.min(curTotal, once)
  window.requestAnimationFrame(function () {
    for (let i = 0; i < pageCount; i++) {
      let li = document.createElement('li')
      li.innerText = curIndex + i + ' : ' + ~~(Math.random() * total)
      ul.appendChild(li)
    }
    loop(curTotal - pageCount, curIndex + pageCount)
  })
}
loop(total, index)

Мониторинг метода Катона

Рассчитайте FPS веб-страницы каждую секунду, получите столбец данных и проанализируйте их. Популярное объяснение состоит в том, что некоторый код JS регулярно выполняется через API requestAnimationFrame. Если браузер завис, частота рендеринга не может быть гарантирована. Если кадр не может достигать 60 кадров за 1 с, это может косвенно отражать частоту кадров рендеринга браузер.

var lastTime = performance.now()
var frame = 0
var lastFameTime = performance.now()
var loop = function (time) {
  var now = performance.now()
  var fs = now - lastFameTime
  lastFameTime = now
  var fps = Math.round(1000 / fs)
  frame++
  if (now > 1000 + lastTime) {
    var fps = Math.round((frame * 1000) / (now - lastTime))
    frame = 0
    lastTime = now
  }
  window.requestAnimationFrame(loop)
}

Мы можем определить некоторые граничные значения, такие как 3 последовательных FPS ниже 20, можно считать, что веб-страница зависла.

рекомендуемая статья