почему javascript не может поймать значение атрибута json [duplicate]

@IBDesignable для UILabel на круговом пути для Swift 2

Большое спасибо как для @Grimxn, так и для @ mark-moeykens за абсолютно убийственную работу. Я сделал небольшой рефактор по работе Марка, поэтому я мог использовать его в проекте, который не успел обновиться до Swift 3. Хотелось поделиться, так как предыдущие сообщения были настолько полезными.

Код Swift 2 для пользовательского UILabel

import UIKit

@IBDesignable
class ArcUILabel: UILabel
{
    // *******************************************************
    // DEFINITIONS (Because I'm not brilliant and I'll forget most this tomorrow.)
    // Radius: A straight line from the center to the circumference of a circle.
    // Circumference: The distance around the edge (outer line) the circle.
    // Arc: A part of the circumference of a circle. Like a length or section of the circumference.
    // Theta: A label or name that represents an angle.
    // Subtend: A letter has a width. If you put the letter on the circumference, the letter's width
    //          gives you an arc. So now that you have an arc (a length on the circumference) you can
    //          use that to get an angle. You get an angle when you draw a line from the center of the
    //          circle to each end point of your arc. So "subtend" means to get an angle from an arc.
    // Chord: A line segment connecting two points on a curve. If you have an arc then there is a
    //          start point and an end point. If you draw a straight line from start point to end point
    //          then you have a "chord".
    // sin: (Super simple/incomplete definition) Or "sine" takes an angle in degrees and gives you a number.
    // asin: Or "asine" takes a number and gives you an angle in degrees. Opposite of sine.
    //          More complete definition: http://www.mathsisfun.com/sine-cosine-tangent.html
    // cosine: Also takes an angle in degrees and gives you another number from using the two radiuses (radii).
    // *******************************************************

    @IBInspectable var angle: CGFloat = 1.6
    @IBInspectable var clockwise: Bool = true

    override func drawRect(rect: CGRect)
    {
        centreArcPerpendicular()
    }

    /**
     This draws the self.text around an arc of radius r,
     with the text centred at polar angle theta
     */
    func centreArcPerpendicular() {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        let str = self.text ?? ""
        let size = self.bounds.size
        CGContextTranslateCTM(context, size.width / 2, size.height / 2)

        let radius = getRadiusForLabel()
        let l = str.characters.count
        let attributes: [String : AnyObject] = [NSFontAttributeName: self.font]

        let characters: [String] = str.characters.map { String($0) } // An array of single character strings, each character in str
        var arcs: [CGFloat] = [] // This will be the arcs subtended by each character
        var totalArc: CGFloat = 0 // ... and the total arc subtended by the string

        // Calculate the arc subtended by each letter and their total
        for i in 0 ..< l {
            arcs += [chordToArc(characters[i].sizeWithAttributes(attributes).width, radius: radius)]
            totalArc += arcs[i]
        }

        // Are we writing clockwise (right way up at 12 o'clock, upside down at 6 o'clock)
        // or anti-clockwise (right way up at 6 o'clock)?
        let direction: CGFloat = clockwise ? -1 : 1
        let slantCorrection = clockwise ? -CGFloat(M_PI_2) : CGFloat(M_PI_2)

        // The centre of the first character will then be at
        // thetaI = theta - totalArc / 2 + arcs[0] / 2
        // But we add the last term inside the loop
        var thetaI = angle - direction * totalArc / 2

        for i in 0 ..< l {
            thetaI += direction * arcs[i] / 2
            // Call centre with each character in turn.
            // Remember to add +/-90º to the slantAngle otherwise
            // the characters will "stack" round the arc rather than "text flow"
            centre(text: characters[i], context: context, radius: radius, angle: thetaI, slantAngle: thetaI + slantCorrection)
            // The centre of the next character will then be at
            // thetaI = thetaI + arcs[i] / 2 + arcs[i + 1] / 2
            // but again we leave the last term to the start of the next loop...
            thetaI += direction * arcs[i] / 2
        }
    }

    func chordToArc(_ chord: CGFloat, radius: CGFloat) -> CGFloat {
        // *******************************************************
        // Simple geometry
        // *******************************************************
        return 2 * asin(chord / (2 * radius))
    }

    /**
     This draws the String str centred at the position
     specified by the polar coordinates (r, theta)
     i.e. the x= r * cos(theta) y= r * sin(theta)
     and rotated by the angle slantAngle
     */
    func centre(text str: String, context: CGContext, radius r:CGFloat, angle theta: CGFloat, slantAngle: CGFloat) {
        // Set the text attributes
        let attributes = [NSForegroundColorAttributeName: self.textColor,
                          NSFontAttributeName: self.font] as [String : AnyObject]
        // Save the context
        CGContextSaveGState(context)
        // Move the origin to the centre of the text (negating the y-axis manually)
        CGContextTranslateCTM(context, r * cos(theta), -(r * sin(theta)))
        // Rotate the coordinate system
        CGContextRotateCTM(context, -slantAngle)

        // Calculate the width of the text
        let offset: CGSize = str.sizeWithAttributes(attributes)
        // Move the origin by half the size of the text
        CGContextTranslateCTM(context, -offset.width / 2, -offset.height / 2)

        // Draw the text
        let txtStr = NSString(string: str)
        txtStr.drawAtPoint(CGPoint(x: 0, y: 0), withAttributes: attributes)

        // Restore the context
        CGContextRestoreGState(context)
    }

    func getRadiusForLabel() -> CGFloat {
        // Imagine the bounds of this label will have a circle inside it.
        // The circle will be as big as the smallest width or height of this label.
        // But we need to fit the size of the font on the circle so make the circle a little
        // smaller so the text does not get drawn outside the bounds of the circle.
        let smallestWidthOrHeight = min(self.bounds.size.height, self.bounds.size.width)
        let heightOfFont = self.text?.sizeWithAttributes([NSFontAttributeName: self.font]).height ?? 0

        // Dividing the smallestWidthOrHeight by 2 gives us the radius for the circle.
        return (smallestWidthOrHeight/2) - heightOfFont + 5
    }
}
368
задан Quentin 11 May 2016 в 22:29
поделиться

6 ответов

Элемент, который вы пытались найти, не был в DOM , когда ваш скрипт работал.

Позиция вашего DOM-зависимого скрипта может оказать глубокое влияние на его поведение. Браузеры анализируют HTML-документы сверху донизу. Элементы добавляются в DOM, и сценарии выполняются (как правило), когда они встречаются. Это означает, что порядок имеет значение. Как правило, скрипты не могут найти элементы, которые появляются позже в разметке, потому что эти элементы еще не добавлены в DOM.

Рассмотрим следующую разметку; сценарий # 1 не находит <div>, а сценарий # 2 преуспевает:

<script>
  console.log("script #1: %o", document.getElementById("test")); // null
</script>
<div id="test">test div</div>
<script>
  console.log("script #2: %o", document.getElementById("test")); // <div id="test" ...
</script>

Итак, что вы должны делать? У вас есть несколько вариантов:


Вариант 1: Переместите свой скрипт

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

<body>
  <button id="test">click me</button>
  <script>
    document.getElementById("test").addEventListener("click", function() {
      console.log("clicked: %o", this);
    });
  </script>
</body><!-- closing body tag -->

Примечание: размещение скриптов внизу как правило, считается лучшей практикой .


Вариант 2: jQuery's ready()

Отмените свой сценарий до тех пор, пока DOM не будет полностью проанализирован, используя ready() :

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).ready(function() {
    $("#test").click(function() {
      console.log("clicked: %o", this);
    });
  });
</script>
<button id="test">click me</button>

Примечание. Вы можете просто привязать к DOMContentLoaded или window.onload, но у каждого есть свои оговорки. jQuery ready() предоставляет гибридное решение.


Вариант 3: Делегирование событий

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

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

jQuery on() выполняет эту логику для нас. Мы просто предоставляем имя события, селектор для желаемого потомка и обработчик событий:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
  $(document).on("click", "#test", function(e) {
    console.log("clicked: %o",  this);
  });
</script>
<button id="test">click me</button>

Примечание: Обычно этот шаблон зарезервированы для элементов, которые не существовали во время загрузки или , чтобы избежать прикрепления большого количества обработчиков. Также стоит отметить, что, пока я прикреплял обработчик к document (для демонстрационных целей), вы должны выбрать ближайшего надежного предка.


Вариант 4: Атрибут defer

Используйте атрибут defer в <script>.

[defer, логический атрибут] установлен для указания на браузера, который должен выполняться после того, как документ был проанализирован.

<script src="https://gh-canon.github.io/misc-demos/log-test-click.js" defer></script>
<button id="test">click me</button>

Для справки, вот код из этого внешнего скрипта :

document.getElementById("test").addEventListener("click", function(e){
   console.log("clicked: %o", this); 
});

Примечание: атрибут defer, безусловно, кажется , как волшебная пуля , но важно знать об оговорках ... 1. defer может использоваться только для внешних скриптов, т. е. для тех, у кого есть атрибут src. 2. знать о поддержке браузера , то есть: ошибка реализации в IE & lt; 10

381
ответ дан canon 19 August 2018 в 18:28
поделиться

Короткие и простые: поскольку элементы, которые вы ищете, не существуют в документе (пока).


В оставшуюся часть этого ответа я буду использовать getElementById как пример, но то же самое относится к getElementsByTagName , querySelector и любому другому методу DOM, который выбирает элементы.

Возможные причины

Есть две причины, по которым элемент может не существовать:

  1. Элемент с переданным идентификатором действительно не существует в документе. Вы должны дважды проверить, что идентификатор, который вы передаете на getElementById, действительно соответствует идентификатору существующего элемента в (сгенерированном) HTML и что у вас не было с ошибкой идентификатор (идентификаторы чувствительный !). Кстати, в большинстве современных браузеров , которые реализуют методы querySelector() и querySelectorAll(), нотация стиля CSS используется для извлечения элемента его id, например: document.querySelector('#elementID'), в отличие от способа, с помощью которого элемент извлекается его id в [[16]; в первом символе # необходимо, во втором это приведет к тому, что элемент не будет извлечен.
  2. Элемент не существует в данный момент , который вы вызываете getElementById ].

Последний случай довольно распространен. Браузеры анализируют и обрабатывают HTML сверху вниз. Это означает, что любой вызов элемента DOM, который встречается до появления этого элемента DOM в HTML, не будет выполнен.

Рассмотрим следующий пример:

<script>
    var element = document.getElementById('my_element');
</script>

<div id="my_element"></div>

Появляется div после script. В настоящий момент сценарий выполняется, элемент не существует , но и getElementById вернут null.

jQuery

То же самое относится к все селекторы с jQuery. jQuery не найдет элементов, если вы ошибочно написали ваш селектор, или вы пытаетесь выбрать их , прежде чем они на самом деле существуют .

Добавленный поворот - это когда jQuery не найден потому, что вы загрузили скрипт без протокола и запускаетесь из файловой системы:

<script src="//somecdn.somewhere.com/jquery.min.js"></script>

этот синтаксис используется, чтобы позволить сценарию загружаться через HTTPS на странице с протоколом https: // и для загрузки HTTP-версии на странице с протоколом http: //

У этого есть неудачный побочный эффект попытки и невозможность загрузить file://somecdn.somewhere.com...


Решения

Прежде чем позвонить getElementById (или любой метод DOM, если на то пошло), убедитесь, что существуют элементы, к которым вы хотите получить доступ, т.е. загружается DOM.

Это может быть обеспечено просто добавив ваш JavaScript после к соответствующему элементу DOM

<div id="my_element"></div>

<script>
    var element = document.getElementById('my_element');
</script>

, и в этом случае вы также можете поместить код непосредственно перед тегом закрывающего тела (</body>) (все DOM элементы будут доступны в момент выполнения скрипта). [/ g3 6]

Другие решения включают прослушивание событий load [MDN] или DOMContentLoaded [MDN] . В этих случаях не имеет значения, где в документе вы помещаете код JavaScript, вам просто нужно запомнить, чтобы весь обработчик DOM обрабатывался в обработчиках событий.

Пример:

window.onload = function() {
    // process DOM elements here
};

// or

// does not work IE 8 and below
document.addEventListener('DOMContentLoaded', function() {
    // process DOM elements here
});

Более подробную информацию об обработке событий и различиях браузера см. в статьях на странице quirksmode.org .

jQuery

Сначала убедитесь, что jQuery загружен правильно , Используйте инструменты разработчика браузера , чтобы узнать, был ли найден файл jQuery и исправлен ли URL-адрес, если он не был (например, добавьте схему http: или https: в начале, отрегулируйте путь, и т. д.)

Прослушивание событий load / DOMContentLoaded - это именно то, что делает jQuery с .ready() [docs] . Весь ваш код jQuery, который влияет на элемент DOM, должен находиться внутри этого обработчика событий.

На самом деле в учебнике j8uery явно указано:

Как почти все, что мы делаем при использовании jQuery, читает или манипулирует объектной моделью документа (DOM), мы должны убедиться, что мы начинаем добавлять события и т. д., как только DOM готов.

Для этого мы регистрируем готовое событие для документа.

$(document).ready(function() {
   // do stuff when DOM is ready
});

В качестве альтернативы вы также можете использовать сокращенный синтаксис:

$(function() {
    // do stuff when DOM is ready
});

Оба эквивалентны.

122
ответ дан 11 revs, 3 users 94% 19 August 2018 в 18:28
поделиться
  • 1
    Стоит ли исправлять последний бит о событии ready, чтобы отразить «более новый», предпочтительный синтаксис, или лучше оставить его как есть, чтобы он работал в более старых версиях? – Tieson T. 25 December 2012 в 10:56
  • 2
    Для jQuery стоит ли добавить альтернативу $(window).load() (и, возможно, синтаксические альтернативы $(document).ready(), $(function(){})? Кажется, это связано, но оно чувствует себя слегка касательным к точке, re создание. – David Thomas 25 December 2012 в 10:58
  • 3
    @David: Хорошая точка с .load. Что касается альтернатив синтаксиса, их можно было бы найти в документации, но поскольку ответы должны быть автономными, на самом деле их стоит добавить. Опять же, я почти уверен, что этот конкретный бит о jQuery уже был дан и, вероятно, рассмотрен в других ответах, и ссылки на него могут быть достаточными. – Felix Kling 25 December 2012 в 11:00
  • 4
    @David: Мне нужно пересмотреть: По-видимому, .load устарел: api.jquery.com/load-event . Я не знаю, только ли это для изображений или window. – Felix Kling 25 December 2012 в 11:03
  • 5
    @David: Не стоит беспокоиться :) Даже если вы не уверены, хорошо, что вы упомянули об этом. Я, вероятно, добавлю несколько простых фрагментов кода, чтобы показать использование. Спасибо за ваш вклад! – Felix Kling 25 December 2012 в 11:05

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

Если вы хотите получить элемент в iframe, вы можете узнать, как здесь .

8
ответ дан Community 19 August 2018 в 18:28
поделиться

Как отметил @FelixKling, наиболее вероятным сценарием является то, что узлы, которые вы ищете, еще не существуют.

Однако современные методы разработки часто могут манипулировать элементами документа за пределами дерева документов либо с DocumentFragments, либо просто отсоединением / повторным подключением текущих элементов напрямую. Такие методы могут использоваться как часть шаблонов JavaScript или для предотвращения чрезмерных операций перерисовки / переплавки, в то время как элементы, о которых идет речь, сильно изменяются.

Аналогично, новая функциональность «Теневой DOM» развертывается в современных браузерах позволяет элементам быть частью документа, но не обрабатываться запросом document.getElementById и всеми его методами sibling (querySelector и т. д.). Это делается для инкапсуляции функциональных возможностей и, в частности, скрыть его.

Опять же, скорее всего, элемент, который вы ищете, просто (пока) в документе, и вы должны сделать, как предлагает Феликс , Тем не менее, вы также должны знать, что это все чаще является не единственной причиной того, что элемент может быть необоснованным (временно или постоянно).

8
ответ дан Nathan Bubna 19 August 2018 в 18:28
поделиться

Причины, почему селекторы на основе id не работают

  1. Элемент / DOM с указанным идентификатором еще не существует.
  2. Элемент существует, но он не является зарегистрированный в DOM [в случае, если HTML-узлы динамически добавляются из ответов Ajax].
  3. Присутствует более одного элемента с тем же идентификатором, который вызывает конфликт.

Решения

  1. Попробуйте получить доступ к элементу после его объявления или, альтернативно, использовать такие вещи, как $(document).ready();
  2. . Для элементов, исходящих из ответов Ajax, используйте метод .bind() для jQuery. В старых версиях jQuery для этого было .live().
  3. Используйте инструменты [например, плагин webdeveloper для браузеров], чтобы найти дубликаты идентификаторов и удалить их.
9
ответ дан Peter Mortensen 19 August 2018 в 18:28
поделиться

Попробуйте поместить document.getElementById в setTimeout()

Например.

setTimeout(function(){
    console.log(document.getElementById('whatever'));
}, 100);

Если это сработает, тогда это просто проблема синхронизации.

-2
ответ дан takataka 19 August 2018 в 18:28
поделиться
  • 1
    Это ненадежно. Это не проблема времени. См. Другие ответы здесь для объяснений реальных решений и . – Xufox 10 August 2018 в 14:05
Другие вопросы по тегам:

Похожие вопросы: