Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pseudoelements #20

Open
jeroenransijn opened this issue Nov 11, 2017 · 12 comments
Open

Support pseudoelements #20

jeroenransijn opened this issue Nov 11, 2017 · 12 comments

Comments

@jeroenransijn
Copy link
Contributor

It would be nice to build in support for before and after elements.

@kdzwinel
Copy link
Collaborator

kdzwinel commented Nov 11, 2017

Getting styles for these pseudo elements, and checking if they exist in the first place, boils down to this: getComputedStyle(someNode, 'after').content.length > 0. Not sure how to calculate bounding client rect for them though 🤔

@KimDal-hyeong
Copy link
Member

KimDal-hyeong commented Nov 11, 2017

Is it the only way to add real element from 'pseudo' to a document?

This code is 'after, before' to span.

  const css = '.before-reset::before, .after-reset::after { content: none !important; }';
  const head = document.head || document.getElementsByTagName('head')[0];
  const style = document.createElement('style');

  style.type = 'text/css';
  style.appendChild(document.createTextNode(css));
  head.appendChild(style);

  const allElements = document.getElementsByTagName('*');

  for (let i = 0; i < allElements.length; i++) {
    const elementBeforeStyles = window.getComputedStyle(allElements[i], ':before');
    const elementAfterStyles = window.getComputedStyle(allElements[i], ':after');
    const elementBeforeContent = elementBeforeStyles.content;
    const elementAfterContent = elementAfterStyles.content;

    if (elementBeforeContent) {
      const virtualBefore = document.createElement('span');

      virtualBefore.setAttribute('style', elementBeforeStyles.cssText);
      virtualBefore.innerHTML = elementBeforeStyles.content.split('"').join('');
      allElements[i].className += ' before-reset';
      allElements[i].prepend(virtualBefore);
    }

    if (elementAfterContent) {
      const virtualAfter = document.createElement('span');

      virtualAfter.setAttribute('style', elementAfterStyles.cssText);
      virtualAfter.innerHTML = elementAfterStyles.content.split('"').join('');
      allElements[i].className += ' after-reset';
      allElements[i].appendChild(virtualAfter);
    }
  }

When I run it, 'clearfix' using 'display: table;' causes problems.
If clearfix is the only pseudo element that uses table, what about excluding it?

@jeroenransijn
Copy link
Contributor Author

@KimDal-hyeong we might be able to see if something is hidden or not based on what we get back from window.getComputedStyle:

  • width
  • height
  • visibility
  • display

Or it might be I am missing something?

I think you people are right the limitations of using getClientBoundingRect. If it's not reliable, I wouldn't mind deprioritizing this. I have some components I want to support that use before and after, but I am able to change that. I think on the long run it's definitely still worth building support before!

@kdzwinel
Copy link
Collaborator

As far as I understand @KimDal-hyeong idea and code, we can replace pseudoelements with "real" elements and continue business as usual. I think this is a really neat workaround 👏, but probably not something solid enough to put into html-sketchapp. I think we should be able to figure out position from CSS props, but this will be tricky.

@kdzwinel
Copy link
Collaborator

kdzwinel commented Mar 19, 2018

Status

Without getBoundingClientRect on a pseudo-elements I don't see how we can implement it (although in devtools it's possible to select it and call getBoundingClientRect on it).

Workaround

Meanwhile @KimDal-hyeong hack that replaces pseudo-elements with spans is the way to go.

@kdzwinel kdzwinel changed the title Support before and after pseudo classes Support pseudoelements Mar 19, 2018
@ryanseddon
Copy link
Contributor

ryanseddon commented Mar 29, 2018

So you can get a pseudo-elements client rect in the dev tools but not sure if this can be automated in puppeteer?

When you click on ::before and then run $0.getBoundingClientRect() that returns the position.

image

@kdzwinel
Copy link
Collaborator

kdzwinel commented Mar 29, 2018

@ryanseddon yeah, in DT it's possible through the DevTools debugging protocol, but html-asketch currently runs in a browser context, so we don't have access to that. It's possible that we will move generating asketch files to puppeteer in the future (where we have access to the protocol and can do a lot of cool stuff), but I wanted to avoid hard dependencies on puppeteer/chrome/DT protocol until it's absolutely needed.

[EDIT] for anyone interested (or future me):

@calebdwilliams
Copy link
Contributor

Is this in active development somewhere? From the thread above I'm not sure if the recommended approach is using the <span> approach or Puppeteer's dev tools protocol. What are the current thoughts?

@ryanseddon
Copy link
Contributor

@calebdwilliams definitely the <span> approach for now.

@alexander-schranz
Copy link

alexander-schranz commented Jul 30, 2018

@KimDal-hyeong thank you for your script. I needed to modify your script with the following changes to get it work for me:

-  const allElements = document.getElementsByTagName('*');
+  const allElements = document.querySelectorAll('body *');
-    if (elementBeforeContent) {
+    if (elementBeforeContent && elementBeforeContent !== 'none') {
-    if (elementAfterContent) {
+    if (elementAfterContent && elementAfterContent !== 'none') {

maybe you can update it with this changes to make it for others easier to use it.

@mattfelten
Copy link

I got some code working right now based on the :before / :after but adding value and placeholder attributes. It took a bit of hackery so tweak to your own pleasure.

// Convert placeholder into span
const generatePlaceholderElement = (el) => {
	if (el.nodeName !== 'INPUT' || el.nodeName !== 'TEXTAREA') return;
	if (!el.hasAttribute('placeholder')) return;

	// Emulation of placeholders
	const elementPlaceholderStyles = window.getComputedStyle(el, ':placeholder');

	const virtualPlaceholder = document.createElement('span');
	virtualPlaceholder.className += 'virtual-placeholder';
	virtualPlaceholder.innerHTML = el.getAttribute('placeholder');
	virtualPlaceholder.style.position = "absolute";
	virtualPlaceholder.style.color = elementPlaceholderStyles.color;
	virtualPlaceholder.style.lineHeight = elementPlaceholderStyles.lineHeight;
	virtualPlaceholder.style.top = (parseInt(elementPlaceholderStyles.borderTopWidth.replace(/px/,'')) + parseInt(elementPlaceholderStyles.paddingTop.replace(/px/,'')) + 'px');
	virtualPlaceholder.style.left = (parseInt(elementPlaceholderStyles.borderLeftWidth.replace(/px/,'')) + parseInt(elementPlaceholderStyles.paddingLeft.replace(/px/,'')) + 'px');

	// Remove Placeholder and set positioning for parent
	el.removeAttribute('placeholder');

	let target = el.parentElement;
	if (target.firstChild != el) {
		var wrapper = document.createElement('div');
		wrapper.className += 'virtual-wrapper';
		target.insertBefore(wrapper, el);
		wrapper.appendChild(el);
		target = wrapper;
	}

	target.style.position = "relative";
	target.appendChild(virtualPlaceholder);
}

// Convert Input Value into span
const generateInputValueElement = (el) => {
	if (el.nodeName !== 'INPUT' || !el.hasAttribute('value')) return;

	const elementValueStyles = window.getComputedStyle(el);

	const virtualValue = document.createElement('span');
	virtualValue.className += 'virtual-value';
	virtualValue.innerHTML = el.getAttribute('value');
	virtualValue.style.position = "absolute";
	virtualValue.style.color = elementValueStyles.color;
	virtualValue.style.lineHeight = elementValueStyles.lineHeight;
	virtualValue.style.top = (parseInt(elementValueStyles.borderTopWidth.replace(/px/,'')) + parseInt(elementValueStyles.paddingTop.replace(/px/,'')) + 'px');
	virtualValue.style.left = (parseInt(elementValueStyles.borderLeftWidth.replace(/px/,'')) + parseInt(elementValueStyles.paddingLeft.replace(/px/,'')) + 'px');

	// Remove Value and set positioning for parent
	el.value = '';
	el.parentElement.style.position = "relative";

	el.parentElement.appendChild(virtualValue);
}

// Convert Input Value into span
const generateTextareaValueElement = (el) => {
	if (el.nodeName !== 'TEXTAREA' || !el.innerHTML) return;

	const elementValueStyles = window.getComputedStyle(el);

	const virtualValue = document.createElement('span');
	virtualValue.className += 'virtual-value';
	virtualValue.innerHTML = el.innerHTML;
	virtualValue.style.position = "absolute";
	virtualValue.style.color = elementValueStyles.color;
	virtualValue.style.lineHeight = elementValueStyles.lineHeight;
	virtualValue.style.top = (parseInt(elementValueStyles.borderTopWidth.replace(/px/,'')) + parseInt(elementValueStyles.paddingTop.replace(/px/,'')) + 'px');
	virtualValue.style.left = (parseInt(elementValueStyles.borderLeftWidth.replace(/px/,'')) + parseInt(elementValueStyles.paddingLeft.replace(/px/,'')) + 'px');

	// Remove Value and set positioning for parent
	el.value = '';
	el.innerHTML = '';

	let target = el.parentElement;
	if (target.firstChild != el) {
		var wrapper = document.createElement('div');
		wrapper.className += 'virtual-wrapper';
		target.insertBefore(wrapper, el);
		wrapper.appendChild(el);
		target = wrapper;
	}

	target.style.position = "relative";
	target.appendChild(virtualValue);
}

getComputedStyle doesn't seem to work with placeholder elements. I tried all the combinations of vendor prefixes and spec that I could think of, but they didn't work, so I'm actually faking the placeholder style with a class. E.g. <input placeholder="text" class="fake-placeholder" /> and the fake-placeholder class has the same style as the psuedoelement.

I also had to wrap the elements so that absolute positioning would work reliably.

@hipstersmoothie
Copy link

hipstersmoothie commented Nov 18, 2018

@KimDal-hyeong @alexander-schranz where exactly should i put that code?

EDIT: I was using story2sketch and was a little confused. The following updated scripts pseudoelements with different colors at different breakpoints

const fixPseudoElements = () => {
  const css = '.before-reset::before, .after-reset::after { content: none !important; }';
  const head = document.head || document.getElementsByTagName('head')[0];
  const style = document.createElement('style');

  style.type = 'text/css';
  style.appendChild(document.createTextNode(css));
  head.appendChild(style);

  const allElements = document.querySelectorAll('body *');
  const oldFakes = document.querySelectorAll('body .fake-pseudo');

  if (oldFakes) {
    Array.from(oldFakes).map(el => el.remove())
  }

  for (let i = 0; i < allElements.length; i++) {
    allElements[i].className = allElements[i].className.replace('before-reset', '')
    allElements[i].className = allElements[i].className.replace('after-reset', '')
    const elementBeforeStyles = window.getComputedStyle(allElements[i], ':before');
    const elementAfterStyles = window.getComputedStyle(allElements[i], ':after');
    const elementBeforeContent = elementBeforeStyles.content;
    const elementAfterContent = elementAfterStyles.content;

    if (elementBeforeContent && elementBeforeContent !== 'none') {
      const virtualBefore = document.createElement('span');
      virtualBefore.className = "fake-pseudo"
      virtualBefore.setAttribute('style', elementBeforeStyles.cssText);
      virtualBefore.innerHTML = elementBeforeStyles.content.split('"').join('');
      allElements[i].className += ' before-reset';
      allElements[i].prepend(virtualBefore);
    }

    if (elementAfterContent && elementAfterContent !== 'none') {
      const virtualAfter = document.createElement('span');
      virtualBefore.className = "fake-pseudo"    
      virtualAfter.setAttribute('style', elementAfterStyles.cssText);
      virtualAfter.innerHTML = elementAfterStyles.content.split('"').join('');
      allElements[i].className += ' after-reset';
      allElements[i].appendChild(virtualAfter);
    }
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants