コード
sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8"/>
<title>has check</title>
<link rel="stylesheet" href="style.css"/>
<script type='module' src='main.js'></script>
</head>
<body>
<div class="has-test">
<input type='checkbox' name='test'/>
<ul></ul>
</div>
<div class='no-support'>
has()使えないよ
</div>
</body>
</html>
sample.css
.has-test:has(ul:empty){
border:2px dashed #aaa;
}
.has-test:has(input[type='checkbox']:checked){
border:2px dashed red;
}
@supports selector(:has(+ *)) {
.no-support {
display: none;
}
}
main.js
import { Has } from './has.js'
class Main{
constructor(){
new Has()
}
}
switch(document.readyState){
case 'complete':
case 'interactive':
new Main()
break
default:
window.addEventListener('DOMContentLoaded',(()=>new Main()))
break
}
has.js
import { Css } from './css.js'
export class Has{
link_files = []
link_value = ""
constructor(){
if(this.is_has()){return}
this.get_style_tag()
this.get_link_tag().then(() => {
const selector_datas = this.get_has(this.link_value)
this.css_set_arr(selector_datas)
})
}
is_has(){
try{
return document.querySelector(`html:has(body)`) ? true : false
}
catch(err){
return false
}
}
get_has(string){
if(!string.trim()){return}
string = string.trim().replace(/(\/\*.*?\*\/)/g , '') // コメントを削除
string = string.replace(/(\r|\n)/g , '') // 改行を削除
const reg = /(.+?)\{(.+?)\}{1,2}/ig
const arr = []
let res
while ((res = reg.exec(string)) !== null){
// @クエリ処理
if(res[1].trim().match(/^@/)){
const media = res[0].trim().match(/@media\s+?(.+?)\{(.*?)\}\}/i)
if(media){
const queries = this.get_has(`${media[2]}}`)
if(queries && queries.length){
for(const query of queries){
query.media_name = '@media'
query.media_selector = media[1]
arr.push(query)
}
}
}
}
else{
// :has()分解処理
const res2 = res[1].match(/(.+?):has+\((.+?)\)(.*?)/i)
if(!res2){continue}
arr.push({
selector_before : res2[1].trim(),
selector_after : res2[3].trim(),
condition : res2[2].trim(),
property : res[2].trim(),
res1 : res,
res2 : res2,
})
}
}
return arr
}
get_style_tag(){
const styles = document.querySelectorAll(`style`)
for(const style of styles){
this.link_value += `${style.textContent}`
}
}
get_link_tag(){
let flg = 0
return new Promise(resolve => {
const links = document.querySelectorAll(`link[rel='stylesheet']`)
for(const link of links){
if(!link.href){continue}
if(!this.is_same_domain(link.href)){continue}
this.load_css_file(link.href, resolve)
flg++
}
if(!flg){
resolve(true)
}
})
}
is_same_domain(url){
const sp = url.split('/')
return sp[2] === location.host ? true : false
}
load_css_file(file, resolve){
this.link_files.push(file)
const xhr = new XMLHttpRequest()
xhr.open('get' , file , true)
xhr.setRequestHeader('Content-Type', 'text/css');
xhr.onload = (e => {
const url = e.target.responseURL
const index = this.link_files.findIndex(e => e === url)
if(index >= 0){
this.link_files.splice(index,1)
}
this.link_value += `${e.target.response}`
// @importを読み込む
const imports = this.get_imports(url, e.target.response)
if(imports){
for(const path of imports){
this.load_css_file(path, resolve)
}
}
if(this.link_files && !this.link_files.length){
resolve(true)
}
})
xhr.send()
}
get_imports(base_url, string){
if(!string){return}
const reg = RegExp("@import\\s+(url\\()*[\'\"\\s]+(.+?)[\'\"\\s]+(\\))*(.*?);(\n)?" , 'ig')
const arr = []
let res
while ((res = reg.exec(string)) !== null){
const url = res[2]
if(!url){continue}
arr.push(this.conv_url(base_url, url))
this.link_value = this.link_value.replace(res[0] , '')
}
return arr
}
conv_url(base_url , target_url){
if(target_url.match(/^http(s):\/\//)){
return target_url
}
const sp = base_url.split('/')
sp[sp.length-1] = target_url
return sp.join('/')
}
get_style_value(){
const styles = document.querySelectorAll('style')
console.log(styles)
let style_value = ""
if(styles){
for(const style of styles){
style_value += `${style.textContent}\n`
}
}
return style_value || null
}
// :hasが機能しないブラウザのために、新たにselectorを登録する。
css_set_arr(selector_datas){
if(!selector_datas || !selector_datas.length){return}
this.monitoring_setting = []
this.observer = new MutationObserver((mutations,e) => {
})
window.addEventListener('click' , this.monitoring.bind(this))
window.addEventListener('resize' , this.monitoring.bind(this))
// for(let i=0; i<selector_datas.length; i++){
for(let i=selector_datas.length-1; i>=0; i--){
this.css_set(selector_datas[i] , i)
}
this.monitoring()
}
css_set(selector_data , num){
const attribute_key = `data-has-${num}`
const selector = `${selector_data.selector_before}[${attribute_key}='true'] ${selector_data.selector_after}`
const reg = RegExp("^(.+?):(.+?)\;?$" , 'i')
let res = null
const stylesheet = Css.get_last_stylesheet() || Css.create_stylesheet()
if(selector_data.media_name){
stylesheet.insertRule(`@media ${selector_data.media_selector}{${selector}{${selector_data.property}}}` , 0)
}
else{
stylesheet.insertRule(`${selector}{${selector_data.property}}` , 0)
}
// ターゲット
const selector_parent = `${selector_data.selector_before} ${selector_data.selector_after}`
const elm = {
parent : document.querySelector(selector_parent),
target : null,
}
if(elm.parent){
elm.target = elm.parent.querySelector(`${selector_data.condition}`)
}
this.observe_init(elm.parent)
this.monitoring_setting.push({
parent : elm.parent,
target_selector : selector_data.condition,
attribute_key : attribute_key,
})
}
monitoring(){
for(const data of this.monitoring_setting){
this.monitoring_check(data.parent , data.target_selector , data.attribute_key)
}
}
monitoring_check(parent, selector_target, attribute_key){
if(!parent){return}
const target = parent.querySelector(selector_target)
parent.setAttribute(attribute_key , target ? true : false)
}
observe_init(parent){
if(!parent){return}
// 監視を開始
this.observer.observe(parent, {
// attributeOldValue: true, // 変化前の属性値を matation.oldValue に格納する
// characterDataOldValue: true, // 変化前のテキストを matation.oldValue に格納する
attributes: true, // 属性変化の監視
characterData: true, // テキストノードの変化を監視
childList: true, // 子ノードの変化を監視
subtree: true, // 子孫ノードも監視対象に含める
})
}
}
css.js
export class Css{
static set_css(selector , property , value){
const rule = Css.get_last_rule(selector)
if(rule){
rule.style.setProperty(property , value , '')
}
else{
Css.create_rule(selector , property , value)
}
}
static get_last_rule(selector){
const rules = Css.get_rules(selector)
return rules && rules.length ? rules[rules.length-1] : null
}
static create_rule(selector , property , value){
const stylesheet = Css.get_last_stylesheet() || Css.create_stylesheet()
stylesheet.insertRule(`${selector}{${property}:${value}}` , 0)
}
static get_last_stylesheet(){
const stylesheets = Css.get_stylesheets()
return stylesheets && stylesheets.length ? stylesheets[stylesheets.length-1] : null
}
static create_stylesheet(){
const style = document.createElement('style')
document.querySelector('head').appendChild(style)
return Css.get_last_stylesheet()
}
static get_ss(selector , property){
const styleSheets = Array.from(document.styleSheets).filter((styleSheet) => !styleSheet.href || styleSheet.href.startsWith(window.location.origin))
let value = null
for(const ss of styleSheets){
if(!ss.cssRules){continue}
for(const cssRule of ss.cssRules){
if(!cssRule.styleSheet || !cssRule.styleSheet.cssRules){continue}
for(const rule of cssRule.styleSheet.cssRules){
if(rule.selectorText !== selector){continue}
value = rule.style[property]
}
}
}
return value;
}
static get_css(selector , property){
const rules = Css.get_rules(selector)
let value = null
for(const rule of rules){
value = rule.style.getPropertyValue(property) || value
}
return value
}
static get_stylesheets(){
return Array.from(document.styleSheets).filter((styleSheet) => !styleSheet.href || styleSheet.href.startsWith(window.location.origin))
}
static get_rules(selector){
const styleSheets = Css.get_stylesheets()
let arr = []
for(const ss of styleSheets){
if(!ss.cssRules){continue}
const res = this.get_rule(ss.cssRules , selector)
if(!res || !res.length){continue}
arr = arr.concat(res)
}
return arr;
}
static get_rule(rules , selector){
if(!rules){return}
let arr = []
for(const rule of rules){
if(rule.selectorText === selector){
arr.push(rule)
}
if(rule.styleSheet && rule.styleSheet.cssRules){
const res = Css.get_rule(rule.styleSheet.cssRules , selector)
if(!res || !res.length){continue}
arr = arr.concat(res)
}
}
return arr;
}
static get_animation_range(selector){
const value = Css.get_css(selector, '--animation-range')
}
static get_rule_properties(property){
const styleSheets = Css.get_stylesheets()
const arr = []
for(const ss of styleSheets){
// tags,files
if(!ss.cssRules){continue}
// selectors
for(const cssRule of ss.cssRules){
if(!cssRule.selectorText){continue}
// styles
for(const style of cssRule.style){
if(style === property){
arr.push(cssRule)
}
}
}
}
return arr;
}
static get_keyframes(animation_name){
const keyframes = Css.get_keyframes2(animation_name)
const arr = []
for(const keyframe of keyframes.cssRules){
keyframe.rate = Number(keyframe.keyText.replace('%',''))
arr.push(keyframe)
}
return arr
}
static get_keyframes2(animation_name){
const styleSheets = Css.get_stylesheets()
const arr = []
for(const ss of styleSheets){
// tags,files
if(!ss.cssRules){continue}
// selectors
for(const cssRule of ss.cssRules){
if(cssRule.name !== animation_name){continue}
return cssRule
}
}
}
}
デモ
has()が使えないブラウザです。
0 件のコメント:
コメントを投稿