Javascript-форум (https://javascript.ru/forum/)
-   Ваши сайты и скрипты (https://javascript.ru/forum/project/)
-   -   CSS генератор на JavaScript (https://javascript.ru/forum/project/41655-css-generator-na-javascript.html)

_0_ 23.09.2013 22:33

CSS генератор на JavaScript
 
Предлагаю обсудить скрипт, генерирующий CSS "на лету".

Всем понятна ущербность CSS в смысле отсутствия классов, констант и переменных (при полной поддержки во всех браузерах), особенно это ощутимо при создании какой-либо иерархии DOM-элементов (например, виджетов).

Сначала хотел сделать парсер, но в javascript проблемно подать многострочный текст при загрузке страницы, да и не силен я в парсерах, поэтому пошел объектным путем. Исходник стилевого листа - это javascript объект, с полями, заполненными по определенным правилам:
cначала идет блок констант, константы имеют флаг - первый символ "$";
потом идут классы, они начинаются с префикса "class_";
далее идут селекторы, их имена приходится заключать в кавычки.

Объект подается в генератор через конструктор function css_object( obj ), конструктор возвращает объект, который имеет метод .write(), записывающий элемент STYLE в поток документа.

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

Константы довольно примитивны, одно правило может иметь только строку или одну константу.

Крайне чувствительный к лишним пробелам, неизбежно возникнет ошибка.

<!DOCTYPE html>
<html>
	<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>CSS JavaScript Object</title>
	
	<script language='javascript'>
			
		function css_object( obj ){
			this.css_data=obj;
			this.trace();
		}

		css_object.prototype={
			
			// определение полей в прототипе не имеет значения, для наглядности
			css_data:null,
			// хранилища
			vars:null,
			classes:null,
			selectors:null,
			css_text:null,
			// приемник строк
			_css:null,
				
			trace:function(){
				
				var vars = this.vars={};
				var classes = this.classes = {};
				var selectors = this.selectors = {};
				var _css = this._css = [];
				var obj=this.css_data;
				
				// разбор полей на типы
				for( var p in obj ){
					if( obj.hasOwnProperty(p) ){
						var n;
						if( p.indexOf('$')==0 ){
							n=p.slice(1);
							vars[p]=obj[p];
						}else if( p.indexOf('class_')==0 ){
							n=p.slice(6)
							classes[n]=obj[p];
						}else{
							selectors[p]=obj[p]
						}
					}
				}
				
				// расширение классов
				for( var class_name in classes ){
					classes[ class_name ]=this._extend_obj( classes[ class_name ] );
				}
						
				// развертывание селекторов
				var selectors2={}; // временное хранилище развернутых селекторов
				var _this=this; // функция требует this
				function deploy_selectors( srs ){

					for( var name in srs ){
						if( srs.hasOwnProperty(name) ){
							
							var selector=srs[name];
							var deferred={}; // хранилище отложенных селекторов
							var d=false; // флаг наличия отложенных селекторов
							
							// заодно расширение селектора
							if( selector.extend ) {
								selector = _this._extend_obj( selector )
								delete selector['extend'];
							}
							
							for( var rule in selector ){
								if( selector.hasOwnProperty(rule) ){
									if( typeof selector[rule] == 'object' ){
										deferred[name+rule]=selector[rule];
										delete selector[rule];
										d=true;
									}
								}
							}
							selectors2[name]=selector;
							if( d ) deploy_selectors( deferred );
						}
					}
				}
				
				deploy_selectors( selectors );
				
				selectors = this.selectors = selectors2;
						
				// запись селекторов в массив строк
				for( var name in selectors ){
					if( selectors.hasOwnProperty(name) ){
						
						var selector=selectors[name];
						if( selector.comment ) {
							_css.push('/* '+selector.comment+' */');
							delete selector['comment'];
						}
						_css.push( name +' {' );
						for( var rule in selector ){
							if( selector.hasOwnProperty(rule) ){
								var value=selector[rule];
								if( typeof value !='object' ){
									_css.push('\t'+rule+' : '+this._parse_value( value )+';');
								}
							}
						}
						_css.push('}\n');
					}
				}

				this.css_text=_css.join('\n');
			},
			
			
			write:function(){
				document.write('<style>\n');
				document.write('/* Cгенерировано javascript */\n');
				document.write( this.css_text );
				document.write('</style>');
			},
			
			// расширение объектов стандартным способом
			_extend_obj:function( obj ){
				
				if( !obj.extend ) return obj;
				
				var top_obj=obj;
				var classes_str=obj.extend;
				obj={};
						
				var classes=this.classes;
				
				var chain=[];
				
				var parents=classes_str.split(',');
				for( var i=0, l=parents.length; i<l; i++ ){
					var class_name=parents[i];
					if( classes[class_name]  && typeof classes[class_name]=='object' ){
						chain.push( classes[class_name] )
					}else{
						var s='Не найден класс '+class_name;
						alert(e);
						throw e;
					}
				}
				chain.push( top_obj );
				
				for( var i=0, l=chain.length; i<l ;i++ ){
					this._extend( obj , chain[i] );
				}
				return obj;
			},
			
			_extend:function( obj, ext ){
				for( var n in ext ){
					if( ext.hasOwnProperty(n) ){
						if( n!='extend' ){
							var v=ext[n]
							if( v === null ){
								delete obj[n];
							}else{
								obj[n]=v;				
							}
						}
					}
				}
				return obj;
			},
			
			_parse_value:function( value ){
				
				if( value.indexOf('$')==0 ){
					
					var vars=this.vars;
					if( vars[value] ){
						return vars[value];
					}else{
						var e='Не найдена переменная '+value;
						alert(e);
						throw e;
					}
				}else{
					return value;
				}
			},
		}


	</script>
	<script language='javascript'>
	
		css=new css_object({
			
			// константы
			
			$widget_border:"1px solid #888",
			$widget_color:"green /* $widget_color */",
			$widget_background_color:"yellow",
			
			$widget_margin:"3px /* $widget_margin */",
			$widget_padding:"1px /* $widget_padding */",
			
			$widget_hover_color:"red /* $widget_hover_color */",
			
			// классы
			
			class_widget:{
				
				"border":"$widget_border",
				"color":"$widget_color",
				"background-color":"$widget_background_color",
				
				"font-family":"Arial",
			},
			
			class_widget_block: { extend:"widget",
			
				"display":"block",
				
				"width":"200px",
				"height":"50px",
				
				"margin":"3px",
				"padding":"3px",
				
				":hover":{
					comment:"Обработка наведения курсора на стандартный блок widget",
					"border-color":"$widget_hover_color"
				}
			},
				
			class_widget_hover:{
				"text-decoration":"none",
				"color":"blue",		
				':hover':{
					comment:"Обработка наведения курсора",
					"text-decoration":"underline",
					"color":"$widget_hover_color",
				},
			},
			
			class_clear_list:{
				comment:"Чистка стиля списков",
				"display":"block",
				"padding":"0px",
				"margin":"0px",
				"list-style-type":"none",
			},
			
			// селекторы
			
			".widget":{ extend:"widget_block",
				
				" .label":{
					"font-size":"12px"
				},
				" a":{ extend:"widget_hover",
					"font-size":"12px",
					"color":"blue"
				},
				" button":{ extend:"widget_hover",
					"font-weight":"bold"
				},
				" div.menu":{
					comment:"Менюшка на css",
					
					"position":"relative",
					"display":"inline-block",
					
					" ul,li":{ extend : "clear_list" },			
								
					" ul":{
						comment:"Все выпадающие блоки",
						"position":"absolute",
						"width":"150px",
						"background-color":"$widget_background_color",
						"display":"none",
					},			
								
					" .label":{ extend : "widget_hover",
						
						"position":"relative",
						
						"line-height":"20px",
						"border":"$widget_border",
						"padding":"0px 5px 0px 5px",
						"cursor":"pointer",
						
						":hover > ul":{
							comment:"Выпадание вложенного блока",
							"display":"block",
							"top":"-1px",
							"left":"100%"
						}
						
					},

					" > ul":{
						comment:"Первый элемент меню",
						"top":"20px",
						"left":"0px",
					},
										
					":hover > ul":{
						comment:"Выпадание первого элемента",
						"display":"block",
					},

				}
			},
			
			
			"textarea#t":{
				comment:"Элемент вывода",
				"width":"600px",
				"height":"300px"
			
			},
			
			
		})

		css.write()

	
	</script>
	
	</head>
	
<body>
<div class='widget'>
	<span class='label'>просто виджет '.widget'</span><br />
	<a href='#'>Ссылка</a>
</div>
<div class='widget'>
	<span class='label'>виджет с кнопкой</span><br />
	<button>Button</button>
</div>
<div class='widget'>
	<span class='label'>виджет с css меню</span><br />
	<div class='menu'>
		<span class='label'>Меню</span>
		<ul>
			<li class='label'>Пункт 1</li>
			<li class='label'>Пункт 2 ...
				<ul>
					<li class='label'>Пункт 2-1</li>
					<li class='label'>Пункт 2-2</li>
					<li class='label'>Пункт 2-3 ...
						<ul style='width:100px'>
							<li class='label'>Пункт 2-3-1</li>
							<li class='label'>Пункт 2-3-1</li>
						</ul>
					</li>
					<li class='label'>Пункт 2-4</li>
				</ul>
			</li>
			<li class='label'>Пункт 3</li>
			<li class='label'>Пункт 4</li>
			
		</ul>
	</div>
</div>
<hr />
<textarea id='t' -style='width:500px;height:300px'></textarea>
<script language='javascript'>
	document.getElementById('t').value=css.css_text;
</script>
</body>
</html>

_0_ 23.09.2013 22:34

Работает без регулярок, вроде быстро, возможно подойдет не только в период разработки, но и для клиента. А что? При определенных навыках, можно изменять объект, в зависимости от "внешних" условий, например версии браузера или размера монитора..

kobezzza 23.09.2013 23:00

http://learnboost.github.io/stylus/

_0_ 23.09.2013 23:13

Это не тоже самое. Я изначально хотел генерировать css в браузере, не на сервере, да и на большой скорости.

Riim 24.09.2013 05:44

а зачем именно на клиенте?

_0_ 24.09.2013 06:36

А почему бы и нет, все под рукой.

kobezzza 24.09.2013 10:54

Цитата:

Сообщение от _0_ (Сообщение 273372)
Это не тоже самое. Я изначально хотел генерировать css в браузере, не на сервере, да и на большой скорости.

Stylus написан на JS, т.е. его также можно использовать на клиенте. Другое дело что такой подход используют лишь при разработке, но никак не на продакшене, т.к. это абсолютно не нужные затраты ресурсов клиента.

Shaci 24.09.2013 13:07

Цитата:

Сообщение от _0_
Я изначально хотел генерировать css в браузере, не на сервере, да и на большой скорости

не стоит это делать на клиенте

Romario5 24.03.2018 11:54

А по моему отличная идея. Можно генерировать не все css, а только те, которые необходимы. Например пользователь перешел в профиль - сгенерировались css для профиля. Плюс ко всему можно менять темы на лету без препроцессоров.


Часовой пояс GMT +3, время: 23:04.