Created
April 29, 2010 11:37
-
-
Save arestov/383477 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* Rocon: library that creates rounded corners | |
* @author Sergey Chikuyonok ([email protected]) | |
* @link http://code.google.com/p/rocon/ | |
*/ | |
/** | |
* Общие методы и свойства для rocon | |
* @author Sergey Chikuyonok ([email protected]) | |
* @copyright Art.Lebedev Studio (http://www.artlebedev.ru) | |
* @include "worker.js" | |
*/ | |
var re_rule = /\.rc(\d+)\b/, | |
re_class = /\brc(\d+(?:\-\d+){0,3})(\-.+?)?\b/, | |
re_shape_flag = /\brc-shape\b/, | |
/** Базовый класс для создаваемых элементов */ | |
base_class = 'rocon', | |
/** Префиск для создаваемых CSS-правил */ | |
rule_prefix = base_class + '__', | |
/** Результат, возвращаемый в объект <code>rocon</code> */ | |
result = { | |
/** | |
* Добавление/обновление уголков для динамически созданных элементов. | |
* Может принимать неограниченное количество элементов либо массивов | |
* элементов, у которых нужно обновить уголки | |
*/ | |
update: function(){}, | |
process: function(context) { | |
processRoundedElements(context); | |
} | |
}, | |
/** @type {CSSStyleSheet} Таблица стилей для уголков */ | |
corners_ss = null, | |
css_text = '', | |
/** Список функций, которые нужно выполнить при загрузке DOM-дерева */ | |
dom_ready_list = [], | |
/** Загрузился ли DOM? */ | |
is_ready = false, | |
/** Привязано ли событие, ожидающее загрузку DOM? */ | |
readyBound = false, | |
userAgent = navigator.userAgent.toLowerCase(), | |
/** Тип и версия браузера пользователя. Взято с jQuery */ | |
browser = { | |
version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1], | |
safari: /webkit/.test( userAgent ), | |
opera: /opera/.test( userAgent ), | |
msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ), | |
mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent ) | |
}; | |
/** код не из основной ветки **/ | |
window.rocon = result; | |
result.update = function(node) { | |
var elems = []; | |
elems.push(node); | |
if (elems.length) { | |
walkArray(elems, function(i, n){ | |
worker.add(this.params, this.node); | |
}); | |
worker.run(); | |
} | |
} | |
/** | |
* Выполняет все функции, добавленные на событие onDomContentLoaded. | |
* Взято с jQuery | |
*/ | |
function fireReady() { | |
//Make sure that the DOM is not already loaded | |
if (!is_ready) { | |
// Remember that the DOM is ready | |
is_ready = true; | |
// If there are functions bound, to execute | |
if ( dom_ready_list.length ) { | |
walkArray(dom_ready_list, function(){ | |
this.call(document); | |
}, true); | |
// Reset the list of functions | |
dom_ready_list = null; | |
} | |
} | |
} | |
/** | |
* Добавляет слушателя на событие onDomContentLoaded | |
* @type {Function} fn Слушатель | |
*/ | |
function addDomReady(fn) { | |
dom_ready_list.push(fn); | |
} | |
/** | |
* Проверка на наступление события onDomContentLoaded. | |
* Взято с jQuery | |
*/ | |
function bindReady(){ | |
if ( readyBound ) return; | |
readyBound = true; | |
if ( document.readyState === "complete" ) { | |
fireReady(); | |
return | |
} | |
// Mozilla, Opera and webkit nightlies currently support this event | |
if ( document.addEventListener ) { | |
// Use the handy event callback | |
document.addEventListener( "DOMContentLoaded", function(){ | |
document.removeEventListener( "DOMContentLoaded", arguments.callee, false ); | |
fireReady(); | |
}, false ); | |
// If IE event model is used | |
} else if ( document.attachEvent ) { | |
// ensure firing before onload, | |
// maybe late but safe also for iframes | |
document.attachEvent("onreadystatechange", function(){ | |
if ( document.readyState === "complete" ) { | |
document.detachEvent( "onreadystatechange", arguments.callee ); | |
fireReady(); | |
} | |
}); | |
// If IE and not an iframe | |
// continually check to see if the document is ready | |
if ( document.documentElement.doScroll && !window.frameElement ) (function(){ | |
if ( is_ready ) return; | |
try { | |
// If IE is used, use the trick by Diego Perini | |
// http://javascript.nwbox.com/IEContentLoaded/ | |
document.documentElement.doScroll("left"); | |
} catch( error ) { | |
setTimeout( arguments.callee, 0 ); | |
return; | |
} | |
// and execute any waiting functions | |
fireReady(); | |
})(); | |
} | |
} | |
/** | |
* Вспомогательная функция, которая пробегается по всем элементам массива | |
* <code>ar</code> и выполняет на каждом элементе его элементе функцию | |
* <code>fn</code>. <code>this</code> внутри этой функции указывает на | |
* элемент массива | |
* @param {Array} ar Массив, по которому нужно пробежаться | |
* @param {Function} fn Функция, которую нужно выполнить на каждом элементе массива | |
* @param {Boolean} forward Перебирать значения от начала массива (п умолчанию: с конца) | |
*/ | |
function walkArray(ar, fn, forward) { | |
if (forward) { | |
for (var i = 0, len = ar.length; i < len; i++) | |
if (fn.call(ar[i], i, ar[i]) === false) | |
break; | |
} else { | |
for (var i = ar.length - 1; i >= 0; i--) | |
if (fn.call(ar[i], i, ar[i]) === false) | |
break; | |
} | |
} | |
/** | |
* Преобразует один массив элементов в другой с помощью функции callback. | |
* Взято в jQuery | |
* @param {Array} elems | |
* @param {Function} callback | |
* @return {Array} | |
*/ | |
function mapArray(elems, callback) { | |
var ret = []; | |
// Go through the array, translating each of the items to their | |
// new value (or values). | |
for ( var i = 0, length = elems.length; i < length; i++ ) { | |
var value = callback( elems[ i ], i ); | |
if ( value != null ) | |
ret[ ret.length ] = value; | |
} | |
return ret.concat.apply( [], ret ); | |
} | |
// TODO Добавить исключение при правильной работе border-radius | |
/** | |
* Преобразует цвет из RGB-предствления в hex | |
* @param {String} color | |
* @return {String} | |
*/ | |
function convertColorToHex(color) { | |
var result; | |
function s(num) { | |
var n = parseInt(num, 10).toString(16); | |
return (n.length == 1) ? n + n : n; | |
} | |
function p(num) { | |
return s(Math.round(num * 2.55)); | |
} | |
if (result = /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/.exec(color)) | |
return '#' + s(result[1]) + s(result[2]) + s(result[3]); | |
// Look for rgb(num%,num%,num%) | |
if (result = /rgb\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*\)/.exec(color)) | |
return '#' + p(result[1]) + p(result[2]) + p(result[3]); | |
// Look for #a0b1c2 | |
if (result = /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/i.exec(color)) | |
return '#' + result[1] + result[2] + result[3]; | |
if (result = /#([a-f0-9])([a-f0-9])([a-f0-9])/i.exec(color)) | |
return '#' + result[1] + result[1] + result[2] + result[2] + result[3] + result[3]; | |
s = null; | |
p = null; | |
return color; | |
} | |
/** | |
* Создает HTML-элемент <code>name</code> с классом <code>class_name</code> | |
* @param {String} name Название элемента | |
* @param {String} class_name Класс элемента | |
* @return {Element} | |
*/ | |
function createElement(name, class_name) { | |
var elem = document.createElement(name); | |
if (class_name) { | |
elem.className = class_name; | |
} | |
return elem; | |
} | |
/** | |
* Простая проверка наличия определенного класса у элемента | |
* @param {HTMLElement} elem | |
* @param {String} class_name | |
* @return {Boolean} | |
*/ | |
function hasClass(elem, class_name) { | |
var re = new RegExp('\\b' + class_name + '\\b'); | |
return elem.nodeType == 1 && re.test(elem.className || ''); | |
} | |
/** | |
* Add new class to the element | |
* @param {String} class_name New class | |
* @param {HTMLElement} elem Element to add class | |
*/ | |
function addClass(class_name, elem) { | |
elem.className += ' ' + class_name; | |
} | |
var toCamelCase = (function(){ | |
var cache = {}, | |
camel = function(str, p1){return p1.toUpperCase();}, | |
re = /\-(\w)/g; | |
return function(name) { | |
if (!cache[name]) | |
cache[name] = name.replace(re, camel); | |
return cache[name]; | |
} | |
})(); | |
/** | |
* Возвращает значение CSS-свойства <b>name</b> элемента <b>elem</b> | |
* @author John Resig (http://ejohn.org) | |
* @param {Element} elem Элемент, у которого нужно получить значение CSS-свойства | |
* @param {String|Array} name Название CSS-свойства | |
* @return {String|Object} | |
*/ | |
function getStyle(elem, name) { | |
var cs, result = {}, n, name_camel, is_array = name instanceof Array; | |
var _name = is_array ? name : [name]; | |
for (var i = 0; i < _name.length; i++) { | |
n = _name[i]; | |
name_camel = toCamelCase(n); | |
// If the property exists in style[], then it's been set | |
// recently (and is current) | |
if (elem.style[name_camel]) { | |
result[n] = result[name_camel] = elem.style[name_camel]; | |
} | |
//Otherwise, try to use IE's method | |
else if (browser.msie) { | |
result[n] = result[name_camel] = elem.currentStyle[name_camel]; | |
} | |
// Or the W3C's method, if it exists | |
else if (document.defaultView && document.defaultView.getComputedStyle) { | |
if (!cs) | |
cs = document.defaultView.getComputedStyle(elem, ""); | |
result[n] = result[name_camel] = cs && cs.getPropertyValue(n); | |
} | |
} | |
// walkArray(name instanceof Array ? name : [name], function(i, n){ | |
// | |
// }); | |
return is_array ? result : result[toCamelCase(name)]; | |
} | |
/** | |
* Разворачивает краткую запись четырехзначного свойства в полную:<br> | |
* — a -> a,a,a,a<br> | |
* — a_b -> a,b,a,b<br> | |
* — a_b_с -> a,b,с,b<br> | |
* | |
* @param {String} prop Значение, которое нужно раскрыть | |
* @return {Array} Массив с 4 значениями | |
*/ | |
function expandProperty(prop) { | |
var chunks = (prop || '').split(/[\-_]+/); | |
switch (chunks.length) { | |
case 1: | |
return [chunks[0], chunks[0], chunks[0], chunks[0]]; | |
case 2: | |
return [chunks[0], chunks[1], chunks[0], chunks[1]]; | |
case 3: | |
return [chunks[0], chunks[1], chunks[2], chunks[1]]; | |
case 4: | |
return chunks; | |
} | |
return null; | |
} | |
/** | |
* Возвращает цвет фона элемента | |
* @type {Function} | |
* @param {Element} elem Элемент, для которого нужно достать цвет фона | |
* @return {String} | |
*/ | |
var getBg = (function() { | |
var session_elems = [], | |
default_color = '#ffffff'; | |
/** | |
* Основной цикл с использованием кэширования | |
*/ | |
function mainLoopCache(elem) { | |
var c; | |
do { | |
if (elem.nodeType != 1) | |
break; | |
if (elem.rocon_bg) { // цвет был найден ранее | |
return elem.rocon_bg; | |
} else { // цвет еще не найден | |
session_elems.push(elem); | |
c = getStyle(elem, 'background-color'); | |
if (c != 'transparent' && c != 'rgba(0, 0, 0, 0)') | |
return convertColorToHex(c); | |
} | |
} while (elem = elem.parentNode); | |
return default_color; | |
} | |
/** | |
* Основной цикл без кэширования | |
*/ | |
function mainLoopNoCache(elem) { | |
var c; | |
do { | |
if (elem.nodeType != 1) | |
break; | |
c = getStyle(elem, 'background-color'); | |
if (c != 'transparent') | |
return convertColorToHex(c); | |
} while (elem = elem.parentNode); | |
return default_color; | |
} | |
return function(elem){ | |
var cl = /* String */elem.className, | |
bg = null; | |
if (getBg.use_cache) { | |
session_elems = []; | |
bg = mainLoopCache(elem); | |
// закэшируем цвет фона у всех элементов, по которым проходились | |
walkArray(session_elems, function(){ | |
this.rocon_bg = bg; | |
getBg.processed_elems.push(this); | |
}); | |
session_elems = null; | |
} else { | |
bg = mainLoopNoCache(elem); | |
} | |
return bg; | |
}; | |
})(); | |
getBg.use_cache = true; | |
getBg.processed_elems = []; | |
var css_rules_cache = []; | |
/** | |
* Добавляет CSS-правило в стиль | |
* @param {String} selector CSS-селектор, для которого нужно добавить правила | |
* @param {String} rules CSS-правила | |
*/ | |
function addRule(selector, rule) { | |
css_rules_cache.push([selector, rule]); | |
} | |
/** | |
* Создает новую таблицу стилей на странице, куда будут добавляться правила | |
* для описания скругленных уголков | |
* @return {CSSStyleSheet} | |
*/ | |
function createStylesheet() { | |
if (!corners_ss) { | |
if (document.createStyleSheet) { | |
corners_ss = document.createStyleSheet(); | |
} else { | |
var style = createElement('style'); | |
document.getElementsByTagName('head')[0].appendChild(style); | |
corners_ss = style.sheet; | |
} | |
} | |
return corners_ss; | |
} | |
function applyCSS() { | |
if (css_rules_cache.length) { | |
var sheet = createStylesheet(); | |
if (sheet.addRule) { | |
// msie way | |
for (var j = 0; j < css_rules_cache.length; j++) { | |
var r = css_rules_cache[j]; | |
sheet.addRule(r[0], r[1]); | |
} | |
} else { | |
// W3C way | |
for (var j = 0; j < css_rules_cache.length; j++) { | |
var r = css_rules_cache[j]; | |
sheet.insertRule(r[0] + '{' + r[1] + '}', j); | |
} | |
} | |
css_rules_cache.length = 0; | |
} | |
} | |
/** | |
* Возвращает массив элементов, которым нужно добавить скругленные уголки. | |
* Элементом массива является объект со свойствами <code>node</code> | |
* и <code>radius</code> | |
* @param {Element} [context] Откуда брать элементы | |
* @return {Array} | |
*/ | |
function getElementsToProcess(context) { | |
var elems = [], m; | |
walkArray((context || document).getElementsByTagName('*'), function(){ | |
if (m = re_class.exec(this.className || '')) { | |
var p = (m[2] || ''); | |
elems.push({ | |
node: this, | |
params: { | |
radius: expandProperty(m[1]), | |
shape: (p.indexOf('shape') != -1 ), | |
force: (p.indexOf('force') != -1 ) | |
} | |
}); | |
} | |
}); | |
return elems; | |
} | |
/** | |
* Обрабатывает все элементы на странице, которым нужно добавить скругленные | |
* уголки | |
*/ | |
function processRoundedElements(context){ | |
createStylesheet(); | |
var elems = getElementsToProcess(context); | |
if (elems.length) { | |
walkArray(elems, function(i, n){ | |
worker.add(this.params, this.node); | |
}); | |
worker.run(); | |
} | |
} | |
/** | |
* Применяет уголки к элементам, переданным в массиве. В основном вызывается из | |
* <code>rocon.update()</code> | |
* @param {arguments} args Аргументы функции | |
* @param {Function} fn Функция, которую нужно выполнить на каждом элементе | |
*/ | |
function applyCornersToArgs(args, fn) { | |
walkArray(args, function(){ | |
walkArray((this instanceof Array) ? this : [this], fn); | |
}); | |
} | |
/** | |
* Делает копию объекта | |
* @param {Object} obj | |
* @return {Object} | |
*/ | |
function copyObj(obj) { | |
var result = {}; | |
for (var p in obj) | |
if (obj.hasOwnProperty(p)) | |
result[p] = obj[p]; | |
return result; | |
} | |
/** | |
* Canvas adapter for drawing rounded corners | |
* @author Sergey Chikuyonok ([email protected]) | |
* @link http://chikuyonok.ru | |
* | |
* @include "common.js" | |
* @include "/js-libs/canvas-doc.js" | |
*/ | |
var canvas = (function(){ | |
var composite_op = 'destination-out', | |
/** @type {Element} Canvas where all images are drawn */ | |
cv, | |
/** @type {Element} Canvas for stroke */ | |
stroke_cv, | |
/** @type {CanvasRenderingContext2D} Drawing context */ | |
ctx, | |
/** @type {CanvasRenderingContext2D} Stroke drawing context */ | |
stroke_ctx, | |
/** Type of returnerd object of <code>draw()</code> method (1 — image string, 2 — canvas element) */ | |
return_type = 1; | |
// debug only | |
// document.body.appendChild(cv); | |
/** | |
* Prepare canvas for drawing: removes old paintings, restores original | |
* transform matrix, scales canvas | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
* @param {String} params.type Corner type: tl, tr, bl, br | |
*/ | |
function prepare(params) { | |
ctx.restore(); | |
cv.width = params.width; | |
cv.height = params.height; | |
ctx.clearRect(0, 0, cv.width, cv.height); | |
ctx.save(); | |
} | |
/** | |
* Draw border shape (stroke) on <code>ctx</code>. Canvas must be already | |
* scaled to the size of corner. | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {String} params.left_color Left border color | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.top_color Top border color | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
*/ | |
function drawStroke(params) { | |
// create short-hand vars for better YUICompressor mungin | |
var radius = params.radius, | |
border_left = params.left, | |
border_top = params.top; | |
if (!(border_left + border_top)) | |
// nothing to draw | |
return; | |
stroke_cv.width = params.width; | |
stroke_cv.height = params.height; | |
stroke_ctx.clearRect(0, 0, params.width, params.height); | |
if (border_top) { | |
stroke_ctx.fillStyle = params.top_color; | |
stroke_ctx.fillRect(radius, 0, cv.width, border_top); | |
} | |
if (border_left) { | |
stroke_ctx.fillStyle = params.left_color; | |
stroke_ctx.fillRect(0, radius, border_left, cv.height); | |
} | |
stroke_ctx.save(); | |
if (params.top_color != params.left_color) { | |
var grd = ctx.createLinearGradient(0, border_top, 0, radius); | |
grd.addColorStop(0, border_top ? params.top_color : params.left_color); | |
grd.addColorStop(1, border_left ? params.left_color : params.top_color); | |
stroke_ctx.fillStyle = grd; | |
} else { | |
stroke_ctx.fillStyle = params.top_color; | |
} | |
// start drawing two circles to create border arc | |
ctx.save(); | |
stroke_ctx.beginPath(); | |
stroke_ctx.arc(radius, radius, radius, 0, Math.PI * 2, true); | |
stroke_ctx.fill(); | |
ctx.restore(); | |
// change composite operation so the next circle will be extracted from the previous one | |
stroke_ctx.globalCompositeOperation = composite_op; | |
stroke_ctx.translate(border_left, border_top); | |
if (radius) { | |
stroke_ctx.scale(Math.max(1 - border_left / radius, 0) || 0.01, Math.max(1 - border_top / radius, 0) || 0.01); | |
} | |
stroke_ctx.beginPath(); | |
stroke_ctx.arc(radius, radius, radius, 0, Math.PI * 2, true); | |
stroke_ctx.fill(); | |
stroke_ctx.restore(); | |
// leave only one quarter of circle | |
stroke_ctx.clearRect(radius, border_top, radius, cv.height); | |
stroke_ctx.clearRect(border_left, radius, cv.width, cv.height); | |
ctx.drawImage(stroke_cv, 0, 0); | |
} | |
/** | |
* Rotate canvas for specified corner type | |
* | |
* @param {String} Corner type: tl, tr, bl, br | |
* @param {Number} radius Corner radius | |
*/ | |
function rotate(params) { | |
switch (params.type) { | |
case 'tr': // top right corner | |
ctx.scale(-1, 1); | |
ctx.translate(-params.width, 0); | |
break; | |
case 'bl': // bottom left corner | |
ctx.scale(1, -1); | |
ctx.translate(0, -params.height); | |
break; | |
case 'br': // bottom right corner | |
ctx.scale(-1, -1); | |
ctx.translate(-params.width, -params.height); | |
break; | |
} | |
} | |
/** | |
* Draw background for corner image | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.color Color of background | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
*/ | |
function drawBackground(params) { | |
var radius = params.radius, | |
offset_x = 0, | |
offset_y = 0; | |
ctx.save(); | |
ctx.fillStyle = params.color; | |
if (!params.use_shape) { | |
ctx.fillRect(0, 0, radius, radius); | |
if (params.left) | |
offset_x = .5; | |
if (params.top) | |
offset_y = .5; | |
ctx.globalCompositeOperation = composite_op; | |
} else { | |
if (params.left) | |
offset_x = 1; | |
if (params.top) | |
offset_y = 1; | |
} | |
ctx.translate(offset_x, offset_y); | |
ctx.beginPath(); | |
ctx.fillStyle = params.color; | |
ctx.arc(radius, radius, radius, 0, Math.PI * 2, true); | |
ctx.closePath(); | |
ctx.fill(); | |
ctx.restore(); | |
if (params.use_shape) { | |
ctx.fillStyle = params.color; | |
ctx.fillRect(radius, 0, cv.width, cv.height); | |
ctx.fillRect(0, radius, cv.width, cv.height); | |
} | |
} | |
return { | |
init: function() { | |
cv = createElement('canvas'); | |
ctx = cv.getContext('2d'); | |
cv.width = 10; | |
cv.height = 10; | |
stroke_cv = createElement('canvas'); | |
stroke_ctx = stroke_cv.getContext('2d'); | |
}, | |
/** | |
* Draw rounded corner | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {String} params.left_color Left border color | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.top_color Top border color | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
* @param {String} params.color Color of background | |
* @param {String} params.type Corner type: tl, tr, bl, br | |
* | |
* @return {String} Image in data:URL format | |
*/ | |
draw: function(params) { | |
// TODO delete this | |
params.width = params.width || params.radius; | |
params.height = params.height || params.radius; | |
if (return_type == 2) { | |
cv = createElement('canvas', 'rocon-cn'); | |
ctx = cv.getContext('2d'); | |
} | |
prepare(params); | |
rotate(params); | |
drawBackground(params); | |
drawStroke(params); | |
return (return_type == 2) ? cv : cv.toDataURL(); | |
}, | |
returnType: function(type) { | |
if (typeof(type) != 'undefined') | |
return_type = type; | |
return return_type; | |
} | |
}; | |
})(); | |
/** | |
* VML adapter for drawing rounded corners | |
* @author Sergey Chikuyonok ([email protected]) | |
* @link http://chikuyonok.ru | |
* | |
* @include "common.js" | |
*/ | |
var vml = (function(){ | |
// init VML (took from Raphaёl lib) | |
var ns = 'rc_vml', | |
doc = document, | |
vml_ns = '', | |
common_style = 'position:absolute;display:inline-block;', | |
is_inited = false; | |
/** Stroke drawing raw string */ | |
var stroke_path = '<' + ns + ':shape ' + vml_ns + ' class="' + ns + ' ' + ns + '_stroke" fillcolor="%top_color%" style="' + common_style + 'width:%width%px;height:%height%px;%offset%;%rotation%" stroked="false" coordsize="%c_width% %c_height%" coordorigin="5 5" ' + | |
'path="m0,%c_height% l0,%radius% qy%radius%,0 l%c_width%,0 r0,%top% l%radius%,%top% qx%left%,%radius% l%left%,%c_height% l0,%c_height% e x">' + | |
'<' + ns + ':fill ' + vml_ns + ' class="' + ns + '" color="%left_color%" color2="%top_color%" type="gradient" colors="%grd_start%% %grd_color_start%, %grd_end%% %grd_color_end%"/>' + | |
'</' + ns + ':shape>', | |
/** Fill drawing raw string */ | |
fill_path = '<' + ns + ':shape ' + vml_ns + ' class="' + ns + '" fillcolor="%color%" stroked="false" style="' + common_style + 'width:%width%px;height:%height%px;%rotation%;%offset%" coordsize="%c_width% %c_height%" coordorigin="5 5" ' + | |
'path="m%offset_x%,%c_height% l%offset_x%,%radius% qy%radius%,%offset_y% l%c_width%,%offset_y% l%c_width%,0 %shape% l0,%c_height% e x"></' + ns + ':shape>', | |
/** All numeric properies of <code>params</code> object */ | |
num_properties = ['top', 'left', 'radius', 'c_width', 'c_height'], | |
/** VML coordinate space multiplier for crispy edges of shapes */ | |
multiplier = 10; | |
/** | |
* Replaces entities (like %entity%) in string with values passed in | |
* <code>params</code>: {entity1: value, entity2: value, ...} | |
* @param {String} str Raw string | |
* @param {Object} params Entity values | |
* @return {String} New string with replaced entities | |
*/ | |
function replaceEntities(str, params) { | |
// merge objects | |
var _params = {}; | |
for (var i = 1; i < arguments.length; i++) { | |
var obj = arguments[i]; | |
if (obj) | |
for (var a in obj) if (obj.hasOwnProperty(a)) | |
_params[a] = obj[a]; | |
} | |
// replace entities | |
return str.replace(/\%(\w+)\%/g, function(s, p1){ | |
return _params[p1]; | |
}); | |
} | |
/** | |
* Create border shape (stroke). | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {String} params.left_color Left border color | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.top_color Top border color | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
* | |
* @return {String} String containing SVG tags | |
*/ | |
function drawStroke(params) { | |
var extra_params = { | |
grd_start: (1 - params.top / params.radius) * 100, | |
grd_color_start: params.top_color, | |
grd_end: (1 - params.radius / params.height) * 100, | |
grd_color_end: params.left_color | |
}; | |
if ((params.type || '').charAt(0) == 'b') { | |
// swap colors | |
var tmp_color = params.top_color; | |
params.top_color = params.left_color; | |
params.left_color = tmp_color; | |
extra_params.grd_start = 100 - extra_params.grd_start; | |
extra_params.grd_end = 100 - extra_params.grd_end; | |
} | |
return (params.top + params.left) ? | |
// replaceEntities(stroke_gradient, params, extra_params) + | |
replaceEntities(stroke_path, params, extra_params) | |
: ''; | |
} | |
/** | |
* Draw background for corner image | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.color Color of background | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
*/ | |
function drawBackground(params) { | |
var t = { | |
shape: params.use_shape ? 'r0,' + params.c_height : 'l0,0' | |
}; | |
return replaceEntities(fill_path, params, t); | |
} | |
/** | |
* Returns transformation params for different types of corners | |
* @param {String} Corner type: tl, tr, bl, br | |
* @param {Number} radius Corner radius | |
*/ | |
function rotate(type, radius) { | |
switch (type) { | |
case 'tr': // top right corner | |
return 'flip:x;margin-left:-1px;'; | |
case 'bl': // bottom left corner | |
return 'flip:y;margin-top:-1px'; | |
case 'br': // bottom right corner | |
return 'rotation:180;margin-left:-1px;margin-top:-1px;'; | |
default: | |
return ''; | |
} | |
} | |
return { | |
init: function() { | |
if (!is_inited) { | |
doc.createStyleSheet().addRule("." + ns, "behavior:url(#default#VML)"); | |
try { | |
!doc.namespaces[ns] && doc.namespaces.add(ns, "urn:schemas-microsoft-com:vml"); | |
} catch (e) { | |
vml_ns = 'xmlns="urn:schemas-microsoft.com:vml"'; | |
} | |
} | |
is_inited = true; | |
}, | |
/** | |
* Draw rounded corner | |
* | |
* @param {Number} params.radius Corner radius | |
* @param {Number} params.left Left border width, pixels | |
* @param {String} params.left_color Left border color | |
* @param {Number} params.top Top border width, pixels | |
* @param {String} params.top_color Top border color | |
* @param {Boolean} params.use_shape Flag, indicating that shape, not counter-shape, must be drawn | |
* @param {String} params.color Color of background | |
* @param {String} params.type Corner type: tl, tr, bl, br | |
* | |
* @return {String} Image in data:URL format | |
*/ | |
draw: function(params) { | |
params = copyObj(params); | |
params.width = params.c_width = params.width || params.radius; | |
params.height = params.c_height = params.height || params.radius; | |
params.offset = ''; | |
if (params.type.charAt(0) == 'b') | |
params.offset += 'top: 0px;'; | |
if (params.type.charAt(1) == 'r') | |
params.offset += 'left: 0px;'; | |
else if (params.use_shape) { | |
params.offset += 'left: ' + Math.max(0, params.radius - params.left - params.opposite.left) + 'px;'; | |
} | |
params.offset_x = (!params.use_shape && params.left) ? 0.5 * multiplier : 0; | |
params.offset_y = (!params.use_shape && params.top) ? 0.5 * multiplier : 0; | |
params.rotation = rotate(params.type); | |
// multiply all numeric values | |
walkArray(num_properties, function(i, n){ | |
params[n] *= multiplier; | |
}); | |
var data = /* '<v:group class="vml" ' + replaceEntities(group_data, params) + '>' + */ | |
drawBackground(params) + | |
drawStroke(params) + | |
/* '</v:group>' */ | |
''; | |
// if (params.type == 'tl') | |
// alert(data); | |
// var elem = createElement('div', 'rocon-cn'); | |
// elem.innerHTML = data; | |
return data; | |
}, | |
returnType: function() { | |
return 2; | |
} | |
}; | |
})(); | |
/** | |
* Main worker and caching mechanism used for storing and applying of drawn | |
* corners to the elements | |
* | |
* @author Sergey Chikuyonok ([email protected]) | |
* @link http://chikuyonok.ru | |
* @include "common.js" | |
* @include "canvas.js" | |
*/ | |
var worker = (function(){ | |
/** @type {canvas} Drawing adapter (canvas, SVG, VML) */ | |
var adapter = null, | |
/** Cached corners */ | |
cache = {}, | |
/** Number of keys in cache */ | |
key_count = 0, | |
/** Native browser properties used to draw browser corners */ | |
native_props = [], | |
queue = [], | |
/** Corner type index in array */ | |
type_pos = {tl: 0, tr: 1, br: 2, bl: 3}, | |
/** CSS properties for corner types */ | |
corner_type_map = { | |
tl: { | |
left: 'border-left-width', | |
left_color: 'border-left-color', | |
top: 'border-top-width', | |
top_color: 'border-top-color' | |
}, | |
tr: { | |
left: 'border-right-width', | |
left_color: 'border-right-color', | |
top: 'border-top-width', | |
top_color: 'border-top-color' | |
}, | |
br: { | |
left: 'border-right-width', | |
left_color: 'border-right-color', | |
top: 'border-bottom-width', | |
top_color: 'border-bottom-color' | |
}, | |
bl: { | |
left: 'border-left-width', | |
left_color: 'border-left-color', | |
top: 'border-bottom-width', | |
top_color: 'border-bottom-color' | |
} | |
}, | |
opposite_corners = { | |
tl: 'tr', | |
tr: 'tl', | |
bl: 'br', | |
br: 'bl' | |
}, | |
css_props = []; | |
//fill-up CSS properties | |
walkArray(['left', 'top', 'right', 'bottom'], function(i, n){ | |
var prefix = 'border-' + n; | |
css_props.push(prefix + '-color', prefix + '-width'); | |
}); | |
/** | |
* Check if we need to add extra width for image (for shape-type corners) | |
* @param {Object} params | |
* @param {String} params | |
* @return {Boolean} | |
*/ | |
function needExtraWidth(params, type) { | |
return params.shape && (type || 'tl').charAt(1) == 'l'; | |
} | |
/** | |
* Returns the smallest numbers | |
* @return {Number} | |
*/ | |
function min() { | |
var args = mapArray(arguments, function(n, i){ | |
return parseInt(n) || 0; | |
}); | |
if (args.length == 2) { | |
return args[0] < args[1] ? args[0] : args[1]; | |
} else { | |
var result = args[0]; | |
for (var i = 1; i < args.length; i++) { | |
if (args[i] < result) | |
result = args[i]; | |
} | |
return result; | |
} | |
} | |
/** | |
* Returns the highest numbers | |
* @return {Number} | |
*/ | |
function max() { | |
var args = mapArray(arguments, function(n, i){ | |
return parseInt(n) || 0; | |
}); | |
if (args.length == 2) | |
return args[0] > args[1] ? args[0] : args[1]; | |
else { | |
var result = args[0]; | |
for (var i = 1; i < args.length; i++) { | |
if (args[i] > result) | |
result = args[i]; | |
} | |
return result; | |
} | |
} | |
/** | |
* Returns key used to store image data | |
* @param {Array|Object} params Corner parameters | |
* @return {String} | |
*/ | |
function cacheKey(params) { | |
var result = []; | |
if (params instanceof Array) | |
for (var i = 0; i < params.length; i++) | |
result.push(params[i]); | |
else | |
for (var a in params) if (params.hasOwnProperty(a)) | |
result.push(a + ':' + params[a]); | |
return result.join(';'); | |
} | |
/** | |
* Add value to cache | |
* @param {String} key Cache key | |
* @param {String} [value] Cache value. If not set, internal class name will be used | |
* @return {String} Internal class name | |
*/ | |
function addToCache(key, value) { | |
if (getCachedValue(key)) | |
return getCachedValue(key); | |
key_count++; | |
cache[key] = value || rule_prefix + key_count; | |
return rule_prefix + key_count; | |
} | |
/** | |
* Returns cached value | |
* @param {String} key cache key | |
* @return {String|null} | |
*/ | |
function getCachedValue(key) { | |
return cache[key]; | |
} | |
/** | |
* Add native CSS corners to the element | |
* @param {Array} radius Set of border radius values | |
* @param {HTMLElement} elem Element to add corners | |
*/ | |
function addNativeCorners(radius, elem) { | |
var key = cacheKey(radius), | |
css_class = getCachedValue(key), | |
css_rules = ''; | |
if (!css_class) { | |
css_class = addToCache(key); | |
walkArray(native_props, function(i, n){ | |
css_rules += n + ':' + radius[i] + 'px;'; | |
}); | |
addRule('.' + css_class, css_rules); | |
} | |
addClass(css_class, elem); | |
} | |
/** | |
* Creates, if necessary, new corner elements and returns them | |
* @param {HTMLElement} elem Container element | |
* @return {Object} | |
*/ | |
function createCornerElements(elem) { | |
var result = {}, | |
found = 0, | |
re = new RegExp('\\b' + base_class + '-([tblr]{2})\\b', 'i'), | |
m; | |
walkArray(elem.childNodes, function(i, /* HTMLElement */ n){ | |
if (m = re.exec(n.className)) { | |
found++; | |
result[m[1]] = n; | |
} | |
}); | |
if (!found) { | |
walkArray(['tl', 'tr', 'bl', 'br'], function(i, n) { | |
var e = createElement('span', base_class + ' ' + base_class +'-' + n); | |
elem.appendChild(e); | |
result[n] = e; | |
}); | |
} | |
return result; | |
} | |
/** | |
* Add element to drawing queue | |
* @param {Object} params Parent element params | |
* @param {HTMLElement} parent Parent element | |
* @param {Object} corners Corner elements | |
*/ | |
function addToQueue(params, parent, corners) { | |
queue.push([params, parent, corners]); | |
} | |
/** | |
* Removes old internal classes from element | |
* @param {HTMLElement} elem | |
* @param {String} new_class New class name to add (optional) | |
* @return {HTMLElement} | |
*/ | |
function cleanUp(elem, new_class) { | |
elem.className = elem.className.replace(/\s*rocon__\d+/, ''); | |
if (new_class) | |
elem.className += ' ' + new_class; | |
return elem; | |
} | |
/** | |
* Returns width that must be set for specified corner type | |
* @param {Object} params Corner's container element params | |
* @param {String} type Corner type (tl, tr, bl, br) | |
* @return {Number} | |
*/ | |
function getCornerWidth(params, type) { | |
return needExtraWidth(params, type) ? 2000 : params.radius[type_pos[type]]; | |
} | |
/** | |
* Returns height that must be set for specified corner type | |
* @param {Object} params Corner's container element params | |
* @param {String} type Corner type (tl, tr, bl, br) | |
* @return {Number} | |
*/ | |
function getCornerHeight(params, type) { | |
var cur_pos = type_pos[type], | |
op_pos = type_pos[ opposite_corners[type] ]; | |
return Math.max(params.radius[cur_pos], params.radius[op_pos]); | |
} | |
/** | |
* Draw corners using adapter | |
* @param {Object} params Parent element params | |
* @param {HTMLElement} parent Parent element | |
* @param {HTMLElement[]} corners Corner elements | |
* @param {Object} style Cached CSS properties | |
*/ | |
function drawCorners(params, parent, corners, style) { | |
// gather all params for all corners | |
var all_params = {}, css_map, elem, style; | |
for (var corner_type in corners) if (corners.hasOwnProperty(corner_type)) { | |
css_map = corner_type_map[corner_type]; | |
/** @type {HTMLElement} */ | |
elem = corners[corner_type]; | |
style = style || getStyle(parent, css_props); | |
// debugger; | |
// gather params for current corner | |
var r = parseInt(params.radius[ type_pos[corner_type] ]); | |
all_params[corner_type] = { | |
radius: r, | |
top: min(style[css_map.top], r), | |
real_top: parseInt(style[css_map.top]) || 0, | |
top_color: convertColorToHex(style[css_map.top_color]), | |
left: min(style[css_map.left], r), | |
real_left: parseInt(style[css_map.left]) || 0, | |
left_color: convertColorToHex(style[css_map.left_color]), | |
color: getBg(params.shape ? parent : parent.parentNode), | |
use_shape: params.shape, | |
type: corner_type, | |
width: max(getCornerWidth(params, corner_type), parseInt(style[css_map.left]) || 0), | |
height: getCornerHeight(params, corner_type) | |
}; | |
} | |
// create corners | |
for (var corner_type in corners) { | |
/** @type {HTMLElement} */ | |
elem = corners[corner_type]; | |
var cparams = all_params[corner_type]; | |
// calculate X and Y offsets | |
cparams.offset_x = -cparams.real_left; | |
cparams.offset_y = -(params.shape ? max(cparams.height, cparams.radius, cparams.real_top) : cparams.real_top); | |
var op_corner = opposite_corners[corner_type], | |
key = cacheKey(cparams); | |
cparams.opposite = all_params[op_corner]; | |
if (needExtraWidth(params, corner_type)) { | |
//add extra properties for cache key | |
key += '--' + (cparams.left + all_params[op_corner].left) + | |
':' + params.radius[ type_pos[op_corner] ]; | |
} | |
var css_class = getCachedValue(key); | |
if (!css_class) { | |
// image is not yet created | |
css_class = addToCache(key); | |
var css_rules = 'height:' + cparams.height + 'px;'; | |
if (adapter.returnType() != 2) // adapter draws image in data:url format | |
css_rules += 'background-image:url(' + adapter.draw(cparams) + ');'; | |
var offset_top = -(params.shape ? Math.max(cparams.height, cparams.radius) : cparams.top); | |
css_rules += ( corner_type.charAt(0) == 't' ? 'top:' : 'bottom:' ) + cparams.offset_y + 'px;'; | |
css_rules += ( corner_type.charAt(1) == 'l' ? 'left:' : 'right:' ) + cparams.offset_x + 'px;'; | |
if (needExtraWidth(params, corner_type)) { | |
var size_delta = max(cparams.left + cparams.opposite.left - cparams.radius, 0); | |
css_rules += 'width:100%;' + | |
'background-position:' + Math.max(0, cparams.radius - cparams.left - cparams.opposite.left) + 'px 0px;' + | |
'left:' + min(-cparams.radius + cparams.opposite.left - size_delta, cparams.offset_x) + 'px;' + | |
'padding-right:' + (size_delta + max(cparams.real_left - cparams.radius, 0)) + 'px'; | |
// css_rules += 'width:100%;' + | |
// 'padding-left:' + (cparams.real_left + all_params[op_corner].left) + 'px;' + | |
// 'clip:rect(auto,auto,auto,' + params.radius[ type_pos[op_corner] ] + 'px);' + | |
// 'background-position:top right;'; | |
} else { | |
css_rules += 'width:' + cparams.width + 'px;'; | |
} | |
addRule('.' + css_class, css_rules); | |
} | |
cleanUp(elem, css_class); | |
if (adapter.returnType() == 2) { | |
var corner = adapter.draw(cparams); | |
if (typeof(corner) == 'string') { | |
elem.innerHTML = corner; | |
} else { | |
elem.innerHTML = ''; | |
elem.appendChild(corner); | |
} | |
} | |
} | |
cleanUp(parent); | |
if (!hasClass(parent, 'rocon-init')) | |
addClass('rocon-init', parent); | |
if (params.shape) | |
adjustBox(params, parent); | |
} | |
/** | |
* Add corners drawn by <code>adapter</code> to the element | |
* @param {Array} params.radius Array of radius values | |
* @param {Boolean} params.shape Use shape instead of counter-shape | |
* @param {HTMLElement} elem Element to apply corners | |
* @param {Boolean} [is_immediate] Draw corners immediately instead adding element to the queue (may cause performance issues in Opera) | |
*/ | |
function addAdapterCorners(params, elem, is_immediate) { | |
/* | |
* Due to stupid Opera bug (http://chikuyonok.ru/playground/opera-bug/) | |
* we need to split this single process into two loops: | |
* 1. Add corner elements to the container | |
* 2. Draw images and apply them to the corners | |
*/ | |
var corners = createCornerElements(elem); | |
if (is_immediate) | |
drawCorners(params, elem, corners); | |
else | |
addToQueue(params, elem, corners); | |
} | |
/** | |
* Main processing function | |
* @param {Array} params.radius Array of radius values | |
* @param {Boolean} params.shape Use shape instead of counter-shape | |
* @param {Boolean} params.force {Boolean} Force usage of drawing adapter instead of native properties | |
* @param {HTMLElement} elem Element to apply corners | |
* @param {Boolean} [is_immediate] Draw corners immediately instead adding element to the queue (may cause performance issues in Opera) | |
*/ | |
function process(params, elem, is_immediate) { | |
if (native_props.length && !params.force) { | |
addNativeCorners(params.radius, elem); | |
} else { | |
addAdapterCorners(params, elem, is_immediate); | |
} | |
} | |
/** | |
* Корректирует CSS-свойства элемента для правильного рисования уголков в виде | |
* формы | |
* @param {Object} params Corner params | |
* @param {HTMLElement} elem Элемент, который нужно подкорректировать | |
*/ | |
function adjustBox(params, elem) { | |
var pt = 'padding-top', | |
pb = 'padding-bottom', | |
mt = 'margin-top', | |
mb = 'margin-bottom', | |
btw = 'border-top-width', | |
bbw = 'border-bottom-width', | |
getProp = function(prop) { | |
return parseInt(elem_styles[prop], 10) || 0; | |
}, | |
addCSSRule = function(property, value) { | |
return property + ':' + value + 'px !important;'; | |
}, | |
elem_styles = getStyle(elem, [pt, pb, mt, mb, btw, bbw]), | |
offset_top = Math.max(params.radius[ type_pos['tl'] ], params.radius[ type_pos['tr'] ]), | |
offset_bottom = Math.max(params.radius[ type_pos['bl'] ], params.radius[ type_pos['br'] ]), | |
offset_border_top = Math.min(offset_top, getProp(btw)), | |
offset_border_bottom = Math.min(offset_bottom, getProp(bbw)); | |
/* | |
* Используем форму, поэтому у блока снижаем верхние и нижние | |
* бордюры, а также на величину радиуса снижаем верхний | |
* и нижний паддинг | |
*/ | |
var padding_top = Math.max(getProp(pt) - offset_top + offset_border_top, 0), | |
padding_bottom = Math.max(getProp(pb) - offset_bottom + offset_border_bottom, 0), | |
margin_top = getProp(mt) + offset_top, | |
margin_bottom = getProp(mb) + offset_bottom, | |
border_top_width = getProp(btw) - offset_border_top, | |
border_bottom_width = getProp(bbw) - offset_border_bottom, | |
key = cacheKey([padding_top, padding_bottom, margin_top, margin_bottom, border_top_width, border_bottom_width]), | |
class_name = getCachedValue(key); | |
if (!class_name) { | |
class_name = addToCache(key); | |
addRule('.' + class_name, | |
addCSSRule(btw, border_top_width) + | |
addCSSRule(bbw, border_bottom_width) + | |
addCSSRule(pt, padding_top) + | |
addCSSRule(pb, padding_bottom) + | |
addCSSRule(mt, margin_top) + | |
addCSSRule(mb, margin_bottom)); | |
} | |
elem.className += ' ' + class_name; | |
} | |
return { | |
/** | |
* Set drawing adapter | |
*/ | |
setAdapter: function(obj) { | |
adapter = obj; | |
if ('init' in adapter) | |
adapter.init(); | |
}, | |
/** | |
* Set native CSS properties for drawing rounded borders | |
* @param {String} tl Top-left corner | |
* @param {String} tr Top-right corner | |
* @param {String} br Bottom-right corner | |
* @param {String} bl Bottom-left corner | |
*/ | |
nativeProperties: function(tl, tr, br, bl) { | |
native_props = [tl, tr, br, bl]; | |
}, | |
/** | |
* Enqueue element for corners addition | |
* @param {Array} params.radius Array of radius values | |
* @param {Boolean} params.shape Use shape instead of counter-shape | |
* @param {Boolean} params.force {Boolean} Force usage of drawing adapter instead of native properties | |
* @param {HTMLElement} elem Element to apply corners | |
*/ | |
add: function(params, elem) { | |
process(params, elem); | |
}, | |
/** | |
* Add corners to enqueued elements | |
*/ | |
run: function() { | |
var start = (new Date).getTime(); | |
walkArray(queue, function(i, n){ | |
drawCorners(n[0], n[1], n[2]); | |
}); | |
var stop1 = (new Date).getTime(); | |
applyCSS(); | |
var stop2 = (new Date).getTime(); | |
// alert('Draw: ' + (stop1 - start) + ' ms, CSS: ' + (stop2 - stop1) + ' ms'); | |
}, | |
/** | |
* Add corners to the element | |
*/ | |
apply: function(params, elem) { | |
process(params, elem, true); | |
} | |
} | |
})(); | |
/** | |
* @author Sergey Chikuyonok ([email protected]) | |
* @link http://chikuyonok.ru | |
* @include "common.js" | |
* @include "worker.js" | |
* @include "canvas.js" | |
* @include "vml.js" | |
*/ | |
addDomReady(processRoundedElements); | |
// после того, как добавили уголки, необходимо очистить кэш фона, | |
// иначе будут проблемы с динамическим обновлением блоков | |
addDomReady(function(){ | |
walkArray(getBg.processed_elems, function(){ | |
this.removeAttribute('rocon_bg'); | |
}); | |
getBg.use_cache = false; | |
}); | |
// set browser-specific properties | |
if (browser.msie) { | |
worker.setAdapter(vml); | |
addDomReady(function(){ | |
// corners_ss.cssText += css_text; | |
// css_text = ''; | |
// addRule = corners_ss.addRule; | |
}); | |
} else { | |
worker.setAdapter(canvas); | |
//canvas.returnType(2); | |
if (browser.safari) { | |
canvas.returnType(1); | |
// worker.nativeProperties( | |
// '-webkit-border-top-left-radius', | |
// '-webkit-border-top-right-radius', | |
// '-webkit-border-bottom-right-radius', | |
// '-webkit-border-bottom-left-radius' | |
// ); | |
} else if (browser.mozilla) { | |
// worker.nativeProperties( | |
// '-moz-border-radius-topleft', | |
// '-moz-border-radius-topright', | |
// '-moz-border-radius-bottomright', | |
// '-moz-border-radius-bottomleft' | |
// ); | |
} | |
//else if (browser.opera) | |
// worker.setAdapter(svg); | |
} | |
bindReady(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment