Подтвердить что ты не робот

Элемент ссылки onload

Есть ли способ прослушать событие onload для элемента <link>?

f.ex:

var link = document.createElement('link');
link.rel = 'stylesheet';
link.href = 'styles.css';

link.onload = link.onreadystatechange = function(e) {
    console.log(e);
};

Это работает для <script> элементов, но не <link>. Есть ли другой способ? Мне просто нужно знать, когда стили во внешней таблице стилей применимы к DOM.

Update:

Будет ли идея вводить скрытый <iframe>, добавить <link> в голову и прослушать событие window.onload в iframe? Он должен запускаться при загрузке css, но может не гарантировать, что он загрузится в верхнем окне...

4b9b3361

Ответ 1

Это своего рода хак, но если вы можете редактировать CSS, вы можете добавить особый стиль (без видимого эффекта), который вы можете прослушать для использования этой техники в этом сообщении: http://www.west-wind.com/weblog/posts/478985.aspx

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

Взлом, как я сказал:)

Ответ 2

Сегодня все современные браузеры поддерживают событие onload на тегах ссылок. Таким образом, я буду охранять хаки, например, создать элемент img и установить onerror:

if !('onload' in document.createElement('link')) {
  imgTag = document.createElement(img);
  imgTag.onerror = function() {};
  imgTag.src = ...;
} 

Это должно обеспечить обходной путь для версий FF-8 и более ранних версий и старых версий Safari и Chrome.

небольшое обновление:

Как отметил Майкл, есть некоторые исключения для браузера, для которых мы всегда хотим применить хак. В Coffeescript:

isSafari5: ->
  !!navigator.userAgent.match(' Safari/') &&
      !navigator.userAgent.match(' Chrom') &&
      !!navigator.userAgent.match(' Version/5.')

# Webkit: 535.23 and above supports onload on link tags.
isWebkitNoOnloadSupport: ->
  [supportedMajor, supportedMinor] = [535, 23]
  if (match = navigator.userAgent.match(/\ AppleWebKit\/(\d+)\.(\d+)/))
    match.shift()
    [major, minor] = [+match[0], +match[1]]
    major < supportedMajor || major == supportedMajor && minor < supportedMinor

Ответ 3

То, как я это делал в Chrome (не тестировалось в других браузерах), - это загрузить CSS с помощью объекта Image и перехватить его событие onerror. Дело в том, что браузер не знает, является ли этот ресурс изображением или нет, поэтому он все равно попытается извлечь его. Однако, поскольку это не фактическое изображение, он вызывает обработчики onerror.

var css = new Image();
css.onerror = function() {
    // method body
}
// Set the url of the CSS. In link case, link.href
// This will make the browser try to fetch the resource.
css.src = url_of_the_css;

Обратите внимание, что если ресурс уже был выбран, этот запрос выборки попадет в кеш.

Ответ 4

например. Android-браузер не поддерживает события "onload" / "onreadystatechange" для элемента: http://pieisgood.org/test/script-link-events/
Но он возвращает:

"onload" in link === true

Итак, мое решение - обнаружить Android-браузер от userAgent, а затем подождать некоторое специальное правило css в вашей таблице стилей (например, reset для полей тела).
Если это не браузер Android и он поддерживает событие "onload", мы будем использовать его:

var userAgent = navigator.userAgent,
    iChromeBrowser = /CriOS|Chrome/.test(userAgent),
    isAndroidBrowser = /Mozilla\/5.0/.test(userAgent) && /Android/.test(userAgent) && /AppleWebKit/.test(userAgent) && !iChromeBrowser; 

addCssLink('PATH/NAME.css', function(){
    console.log('css is loaded');
});

function addCssLink(href, onload) {
    var css = document.createElement("link");
    css.setAttribute("rel", "stylesheet");
    css.setAttribute("type", "text/css");
    css.setAttribute("href", href);
    document.head.appendChild(css);
    if (onload) {
        if (isAndroidBrowser || !("onload" in css)) {
            waitForCss({
                success: onload
            });
        } else {
            css.onload = onload;
        }
    }
}

// We will check for css reset for "body" element- if success-> than css is loaded
function waitForCss(params) {
    var maxWaitTime = 1000,
        stepTime = 50,
        alreadyWaitedTime = 0;

    function nextStep() {
        var startTime = +new Date(),
            endTime;

        setTimeout(function () {
            endTime = +new Date();
            alreadyWaitedTime += (endTime - startTime);
            if (alreadyWaitedTime >= maxWaitTime) {
                params.fail && params.fail();
            } else {
                // check for style- if no- revoke timer
                if (window.getComputedStyle(document.body).marginTop === '0px') {
                    params.success();
                } else {
                    nextStep();
                }
            }
        }, stepTime);
    }

    nextStep();
}

Демо: http://codepen.io/malyw/pen/AuCtH

Ответ 5

Так как вам не понравился мой хак:), я огляделся по-другому и нашел один брачным письмом.

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

Ответ 6

Другой способ сделать это - проверить, сколько загружено таблиц стилей. Например:

С "css_filename" URL или имя файла css и "callback" функция обратного вызова при загрузке css:

var style_sheets_count=document.styleSheets.length;
var css = document.createElement('link');
css.setAttribute('rel', 'stylesheet');
css.setAttribute('type', 'text/css');
css.setAttribute('href', css_filename);
document.getElementsByTagName('head').item(0).appendChild(css);
include_javascript_wait_for_css(style_sheets_count, callback, new Date().getTime());

function include_javascript_wait_for_css(style_sheets_count, callback, starttime)
/* Wait some time for a style sheet to load.  If the time expires or we succeed
 *  in loading it, call a callback function.
 * Enter: style_sheet_count: the original number of style sheets in the
 *                           document.  If this changes, we think we finished
 *                           loading the style sheet.
 *        callback: a function to call when we finish loading.
 *        starttime: epoch when we started.  Used for a timeout. 12/7/11-DWM */
{  
   var timeout = 10000; // 10 seconds
   if (document.styleSheets.length!=style_sheets_count || (new Date().getTime())-starttime>timeout)
      callback();
   else
      window.setTimeout(function(){include_javascript_wait_for_css(style_sheets_count, callback, starttime)}, 50);
}

Ответ 7

Вам нужен либо определенный элемент, какой стиль вы знаете, либо если вы управляете файлом CSS, вы можете вставить для этого фиктивный элемент. Этот код точно выполнит ваш обратный вызов, когда содержимое файла css будет применено к DOM.

// dummy element in the html
<div id="cssloaded"></div>

// dummy element in the css
#cssloaded { height:1px; }

// event handler function
function cssOnload(id, callback) {
  setTimeout(function listener(){
    var el = document.getElementById(id),
        comp = el.currentStyle || getComputedStyle(el, null);
    if ( comp.height === "1px" )
      callback();
    else
      setTimeout(listener, 50);
  }, 50)
}

// attach an onload handler
cssOnload("cssloaded", function(){ 
  alert("ok"); 
});

Если вы используете этот код в нижней части документа, вы можете переместить переменные el и comp вне таймера, чтобы получить элемент один раз. Но если вы хотите прикрепить обработчик где-нибудь в документе (например, голова), вы должны оставить код как есть.

Примечание: проверено на FF 3+, IE 5.5+, Chrome

Ответ 8

Этот трюк заимствован из плагин xLazyLoader jQuery:

var count = 0;

(function(){
  try {
    link.sheet.cssRules;
  } catch (e) {
    if(count++ < 100)
      cssTimeout = setTimeout(arguments.callee, 20);
    else
      console.log('load failed (FF)');
    return;
  };
  if(link.sheet.cssRules && link.sheet.cssRules.length == 0) // fail in chrome?
    console.log('load failed (Webkit)');
  else
    console.log('loaded');
})();

Протестировано и работает локально в FF (3.6.3) и Chrome (linux - 6.0.408.1 dev)

Демо здесь (обратите внимание, что это не будет работать для загрузки css-сайта, как это сделано в демо, под FF)

Ответ 9

Плагин xLazyLoader терпит неудачу, поскольку свойства cssRules скрыты для таблиц стилей, принадлежащих другим доменам (разрывает ту же самую политику происхождения). Так что вам нужно сделать, это сравнить ownerNode и owningElements.

Вот подробное объяснение того, что делать: http://yearofmoo.com/2011/03/cross-browser-stylesheet-preloading/

Ответ 10

// this work in IE 10, 11 and Safari/Chrome/Firefox/Edge
// if you want to use Promise in an non-es6 browser, add an ES6 poly-fill (or rewrite to use a callback)

let fetchStyle = function(url) {
  return new Promise((resolve, reject) => {
    let link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.onload = resolve;
    link.href = url;

    let headScript = document.querySelector('script');
    headScript.parentNode.insertBefore(link, headScript);
  });
};

Ответ 11

Предположительно, цель динамической загрузки CSS позже - ускорить загрузку страницы, так что CSS-скрипты с "звонками и свистами" загружаются позже, или CSS загружается по требованию, когда пользователь взаимодействует с компонентом, который этого требует.

Решения с участием setTimeout/setInterval и опроса являются отвратительными, и в первом случае почти наверняка сделайте загрузку страницы более медленной для фактического пользователя, который не является GoogleBot, особенно, поскольку они, вероятно, используют мобильный телефон - в основном только браузеры WebKit (iOS, Android, Chrome) не поддерживали событие load на link элементах во время публикации этого вопроса (особенно IE сделал это с IE7 или ранее). Теперь, в 2017 году, нам все равно нужно обслуживать тех многих пользователей мобильных телефонов, которые не обновляют свой телефон каждые два или два года (так же, как нам приходилось обслуживать людей, использующих IE6/7/8 для yonks и несколько по-прежнему - возможно, еще 8% [восемь процентов!] по всему миру в соответствии с некоторыми статистическими мониторами).

К счастью, существует бесконечно лучшее резервное решение. Мы можем создать invisble iframe и прослушать resize события на contentWindow. Если вы контролируете загружаемый CSS, вы можете просто добавить правило, которое изменит размер невидимого iframe, чтобы событие resize срабатывало при загрузке CSS. Даже если вы этого не сделаете, вы можете найти какое-то правило в загружаемом CSS, который вряд ли может существенно измениться в следующей версии, и что приведет к изменению размера вашего iframe, который вы могли бы использовать.

Пример (который обслуживает оба события и срабатывает только один раз):

В основном CSS (загружается через прямое включение в <head>)

.mycss-load-event-listener {
  width: 100%;
}

В "Мой CSS" (загружен позже)

.mycss-load-event-listener {
  width: 0;
}

Загрузка JavaScript "Мой CSS" позже (с использованием jQuery для простоты)

/** General purpose function, extend to your needs */
var loadCss = function(path, id, listener) {
  // Call listener only once
  var listenerCalled;
  var dualListener = function(e) {
    // TODO: comment this out for production
    console.log(e); // in modern browsers you'll see `load` and `resize`
    if (!listenerCalled) {
      listener(e);
      listenerCalled = true;
    }
  }
  // Add the invisible iframe listener for WebKit
  $('<iframe/>').attr({
    tabindex: -1,
    'class': id + '-load-event-listener'
  }).css({
    position: 'fixed',
    // width: '100%'; // omitted to allow override by loaded CSS
    height: 0,
    bottom: 0,
    border: 0,
    backgroundColor: 'transparent'
  }).on('load', function() {
    $(this.contentWindow).resize(dualListener);
  }).appendTo('body');
  // Add the element as normal, with `load` listener for Browsers
  $('<link/>').attr({
    rel: 'stylesheet',
    // id: id, // if you have some other use for identifying the element
    href: path,
    type: 'text/css',
    media: 'all'
  }).appendTo('head').on('load', dualListener);
};

// Load 'My CSS', 'mycss' is the prefix to class names to trigger event
loadCss('/path/to/mycss.css', 'mycss', function() {
  // Do what you wanted to do
});

В современных браузерах вы, вероятно, увидите событие load, которое сразу же следует за событием resize. В старых браузерах вы можете видеть только один или другой, но вы также накрыли обе эти базы.

Я тестировал эту технику, и она работает (хотя я все же хотел бы убедиться, что нет возможности сделать угловой случай, когда событие iframe load срабатывает слишком поздно, чтобы настроить resize listener - решение этого вопроса, если это может быть проблемой, не является прямым примером перемещения кода загрузки CSS внутри прослушивателя iframe load, поскольку не все браузеры запускают событие iframe load). (Я не тестировал этот конкретный пример кода, который я собрал, например, просто, могут быть незначительные синтаксические ошибки и т.д.)

Скрытая техника iframe resize также может использоваться во многих других сценариях, чтобы избежать опроса, который может вызвать невосприимчивый пользовательский интерфейс и ужасный UX.

Ирония заключается в том, что Google PageSpeed ​​поощряет задержку загрузки CSS, но Chrome/WebKit только недавно внедрил событие, которое позволяет эффективно использовать его (оттенки IE?). Но, пожалуйста, не переусердствуйте; даже если он дает некоторые SEO сегодня, он не будет держать клиентов, если вы получите его немного неправильно, что неизбежно вы будете, как и большинство крупных коммерческих сайтов сегодня. Загрузите основные шрифты и макет в основной CSS, включенный непосредственно в <head>. Веб-страница, которая пока слабее для отображения, обеспечивает гораздо лучший UX, чем тот, где текст скачет повсюду в течение довольно долгого времени после того, как вы уже начали его читать!

Ответ 12

Это кросс-браузерное решение

// Your css loader
var d = document,
    css = d.head.appendChild(d.createElement('link'))

css.rel = 'stylesheet';
css.type = 'text/css';
css.href = "https://unpkg.com/[email protected]/css/tachyons.css"

// Add this  
if (typeof s.onload != 'undefined') s.onload = myFun;
} else {
    var img = d.createElement("img");
    img.onerror = function() {
      myFun();
      d.body.removeChild(img);
    }
    d.body.appendChild(img);
    img.src = src;
}

function myFun() {
    /* ..... PUT YOUR CODE HERE ..... */
}

Ответ основан на этой ссылке, которая говорит:

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

В современных браузерах вы можете сделать css.onload и добавить этот код в качестве запасного варианта, чтобы охватить старые браузеры до 2011 года, когда только Opera и Internet Explorer поддерживали событие onload и onreadystatechange соответственно.

Примечание: я тоже ответил здесь, и это мой дубликат, и он заслуживает наказания за мою честность: P