Большое спасибо как для @Grimxn, так и для @ mark-moeykens за абсолютно убийственную работу. Я сделал небольшой рефактор по работе Марка, поэтому я мог использовать его в проекте, который не успел обновиться до Swift 3. Хотелось поделиться, так как предыдущие сообщения были настолько полезными.
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
}
}
Элемент, который вы пытались найти, не был в 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>
Итак, что вы должны делать? У вас есть несколько вариантов:
Переместите свой скрипт дальше по странице, перед закрывающим тегом тела. Организованный таким образом остальная часть документа анализируется до того, как будет выполнен ваш скрипт:
<body>
<button id="test">click me</button>
<script>
document.getElementById("test").addEventListener("click", function() {
console.log("clicked: %o", this);
});
</script>
</body><!-- closing body tag -->
Примечание: размещение скриптов внизу как правило, считается лучшей практикой .
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()
предоставляет гибридное решение.
Делегированные события имеют преимущество в том, что они могут обрабатывать события из элементов-потомков, которые будут добавлены в документ позже.
blockquote>Когда элемент вызывает событие (при условии, что это 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>
.[
blockquote>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
Короткие и простые: поскольку элементы, которые вы ищете, не существуют в документе (пока).
В оставшуюся часть этого ответа я буду использовать getElementById
как пример, но то же самое относится к getElementsByTagName
, querySelector
и любому другому методу DOM, который выбирает элементы.
Возможные причины
Есть две причины, по которым элемент может не существовать:
getElementById
, действительно соответствует идентификатору существующего элемента в (сгенерированном) HTML и что у вас не было с ошибкой идентификатор (идентификаторы чувствительный !). Кстати, в большинстве современных браузеров , которые реализуют методы querySelector()
и querySelectorAll()
, нотация стиля CSS используется для извлечения элемента его id
, например: document.querySelector('#elementID')
, в отличие от способа, с помощью которого элемент извлекается его id
в [[16]; в первом символе #
необходимо, во втором это приведет к тому, что элемент не будет извлечен. 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 готов.
Для этого мы регистрируем готовое событие для документа.
blockquote>$(document).ready(function() { // do stuff when DOM is ready });
В качестве альтернативы вы также можете использовать сокращенный синтаксис:
$(function() { // do stuff when DOM is ready });
Оба эквивалентны.
ready
, чтобы отразить «более новый», предпочтительный синтаксис, или лучше оставить его как есть, чтобы он работал в более старых версиях?
– Tieson T.
25 December 2012 в 10:56
$(window).load()
(и, возможно, синтаксические альтернативы $(document).ready()
, $(function(){})
? Кажется, это связано, но оно чувствует себя i> слегка касательным к точке, re создание.
– David Thomas
25 December 2012 в 10:58
.load
. Что касается альтернатив синтаксиса, их можно было бы найти в документации, но поскольку ответы должны быть автономными, на самом деле их стоит добавить. Опять же, я почти уверен, что этот конкретный бит о jQuery уже был дан и, вероятно, рассмотрен в других ответах, и ссылки на него могут быть достаточными.
– Felix Kling
25 December 2012 в 11:00
.load
устарел: api.jquery.com/load-event . Я не знаю, только ли это для изображений или window
.
– Felix Kling
25 December 2012 в 11:03
Если элемент, к которому вы пытаетесь получить доступ, находится внутри iframe
, и вы пытаетесь получить к нему доступ за пределами контекста iframe
, это также приведет к сбою.
Если вы хотите получить элемент в iframe, вы можете узнать, как здесь .
Как отметил @FelixKling, наиболее вероятным сценарием является то, что узлы, которые вы ищете, еще не существуют.
Однако современные методы разработки часто могут манипулировать элементами документа за пределами дерева документов либо с DocumentFragments, либо просто отсоединением / повторным подключением текущих элементов напрямую. Такие методы могут использоваться как часть шаблонов JavaScript или для предотвращения чрезмерных операций перерисовки / переплавки, в то время как элементы, о которых идет речь, сильно изменяются.
Аналогично, новая функциональность «Теневой DOM» развертывается в современных браузерах позволяет элементам быть частью документа, но не обрабатываться запросом document.getElementById и всеми его методами sibling (querySelector и т. д.). Это делается для инкапсуляции функциональных возможностей и, в частности, скрыть его.
Опять же, скорее всего, элемент, который вы ищете, просто (пока) в документе, и вы должны сделать, как предлагает Феликс , Тем не менее, вы также должны знать, что это все чаще является не единственной причиной того, что элемент может быть необоснованным (временно или постоянно).
Причины, почему селекторы на основе id не работают
Решения
$(document).ready();
.bind()
для jQuery. В старых версиях jQuery для этого было .live()
. Попробуйте поместить document.getElementById
в setTimeout()
Например.
setTimeout(function(){
console.log(document.getElementById('whatever'));
}, 100);
Если это сработает, тогда это просто проблема синхронизации.