<section class='panel'>
  <section class='controls'>
    <fieldset class='sub'>
      <legend>
        axis
        <div class='infopop'>the axis along which we skew the element</div>
      </legend>

      <input name='axis' type='radio' id='x' 
             class='ac' checked/>
      <label for='x'><var>x</var> axis</label>

      <input name='axis' type='radio' id='y' 
             class='ac'/>
      <label for='y'><var>y</var> axis</label>
    </fieldset>
    
    <section class='sub'>
      <label for='a'>
        angle
        <div class='infopop'>the skew angle</div>
      </label>
      <input type='range' id='a' min='-89' max='89'/>
    </section>
  </section>
  
  <pre class='codebox'>transform: skewX(0deg);</pre>
</section>

<section class='panel'>
  <svg viewbox='-512 -512 1024 1024'>
    <defs>
      <path id='sqb' 
            d='M-256 -256 L256 -256 
               L240 -240 L-240 -240z' />
    </defs>

    <g class='initial'>
      <g class='square'>
        <use class='sqb' 
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(90)' 
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(180)'
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(270)'
             xlink:href='#sqb' />
      </g>
      <g class='coord-syst'>
        <path d='M-500 0 L490 0
                 L485 -10 L500 0 L485 10 L490 0
                 M0 -500 L0 490
                 L-10 485 L0 500 L10 485 L0 490'/>
        <text x='500' y='32'
              text-anchor='end'>x</text>
        <text x='16' y='500'>y</text>
      </g>
    </g>
    <g class='final dir--x'>
      <g class='square'>
        <use class='sqb' 
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(90)' 
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(180)'
             xlink:href='#sqb' />
        <use class='sqb' transform='rotate(270)'
             xlink:href='#sqb' />
      </g>
      <g class='coord-syst'>
        <path class='axis axis--x' 
              d='M-500 0 L490 0
                 L485 -10 L500 0 L485 10 L490 0'/>
        <path class='axis axis--y' 
              d='M0 -500 L0 490
                 L-10 485 L0 500 L10 485 L0 490'/>
        <text class='axis--x' x='500' y='32'
              text-anchor='end'>x</text>
        <text class='axis--y' x='16' y='500'>y</text>
      </g>
    </g>
    
    <circle cx='192' cy='192' class='point initial' r='8' />
    <circle cx='192' cy='192' class='point final' r='8' />
    <path class='conn' d='M192 192 L192 192' />
    
    <path class='arc' d='' />
  </svg>
</section>
@import "compass/css3";

@mixin track() {
  width: 14em; height: .375em;
  background: #333;
}

@mixin thumb() {
  border: none;
  border-radius: 0;
  width: 1em; height: 1em;
  box-shadow: inset 0 0 0 3px #333;
  background: #ccc;
}

@mixin thumb-focus() {
  background: #4ca454;
}

html { min-width: 15em; }

svg {
  display: block;
  overflow: visible;
  margin: 0 auto;
  min-width: 15em; min-height: 15em;
  width: 90vmin; height: 90vmin;
}

.initial { opacity: .32; }

.sqb {
  .dir--x &:nth-child(even), 
  .dir--y &:nth-child(odd) {
    fill: #be4c39;
  }
}

.coord-syst {
  stroke: #000;
  
  path { stroke-width: 2; }
  
  .dir--x & .axis--x, .dir--y & .axis--y {
    fill: #4472b9;
    stroke: #4472b9;
  }
  .dir--x & .axis--y, .dir--y & .axis--x {
    fill: #4ca454;
    stroke: #4ca454;
  }
  
  text {
    font: italic 2em serif;
  }
}

.conn, .arc {
  fill: darkorange;
  stroke: darkorange;
  stroke-width: 2;
}



.infopop {
  position: absolute;
  transform: scale(0);
}

input[type='radio'] {
  &, + label { cursor: pointer; }
}

input[type='range'] {
  &, 
  &::-webkit-slider-runnable-track, 
  &::-webkit-slider-thumb {
    -webkit-appearance: none;
  }  
  
  display: block;
  padding: 0;
  width: 14em; height: 2em;
  background: transparent;
  font-size: 1em;
  cursor: pointer;
  
  &::-webkit-slider-runnable-track {
    @include track();
  }
  &::-moz-range-track {
    @include track();
  }
  &::-ms-track {
    @include track();
    color: transparent;
  }
  &::-ms-fill-lower, 
  &::-ms-tooltip { display: none; }
  
  &::-webkit-slider-thumb {
    margin-top: -.3125em;
    @include thumb();
  }
  &::-moz-range-thumb {
    @include thumb();
  }
  &::-ms-thumb {
    @include thumb();
  }
  
  &:focus {
    outline: solid 0 transparent;
    
    &::-webkit-slider-thumb {
      @include thumb-focus();
    }
    &::-moz-range-thumb {
      @include thumb-focus();
    }
    &::-ms-thumb {
      @include thumb-focus();
    }
  }
}

.codebox {
  padding: .5em;
  background: #1d1f21;
}

.token {
  &--property { color: #9b869c; }
  &--punctuation { color: #ccc; }
  &--value { color: #cd6a51; }
  &--function { color: #4ca454; }
  &--argument { color: #fff; }
  &--number { color: #e18728; }
  &--unit { color: #be4c39; }
}

@media (min-aspect-ratio: 1/1) {
  .panel:first-of-type {
    float: right;
  }
}

@media (max-aspect-ratio: 1/1) {
  @media (min-width: 30em) {
    .controls {
      overflow: hidden;
    }
    .sub {
      float: left;
    }
  }
  
  @media (min-width: 45em) {
    .controls, .codebox {
      float: left;
    }
    .panel:first-of-type {
      overflow: hidden;
    }
  }
}
Object.getOwnPropertyNames(Math).map(function(p) {
  window[p] = Math[p];
});

if(!sign) {
  var sign = function(n) { return n/abs(n); };
}

var rad = function(val, unit) {
  var val = val || 0, 
      unit = 'deg';
  
  if(unit === 'deg') { return val*PI/180; }
  if(unit === 'rad') { return val; }
  if(unit === 'turn') { return val*2*PI; }
  if(unit === 'grad') { return val*PI/200; }
};

var deg = function(val, unit) {
  var val = val || 0, 
      unit = 'rad';
  
  if(unit === 'deg') { return val; }
  if(unit === 'rad') { return val*180/PI; }
  if(unit === 'turn') { return val*360; }
  if(unit === 'grad') { return val*9/10; }
};

Node.prototype.setAttrs = function(attr_obj) {
  for(var prop in attr_obj) {
    this.setAttribute(prop, attr_obj[prop]);
  }
};

var target = document.querySelector('g.final'), 
    codebox = document.querySelector('.codebox'),
    sar = document.getElementById('a'), 
    ac = document.querySelectorAll('.ac'), 
    sel1 = '.point.initial', 
    p1 = document.querySelector(sel1), 
    sel2 = '.point.final', 
    p2 = document.querySelector(sel2), 
    cl = document.querySelector('.conn'), 
    arc = document.querySelector('.arc'), 
    α = rad(sar.value), 
    axis = 'X', r = 80;

var tokenize = function(str) {
  var s = "<span class='token token--", 
      m = "'>", e = '</span>', 
      f = ' contenteditable ',
      tt = [ /* token types */
        'property', 'punctuation', 'value', 
        'function', 'argument', 'number', 'unit'
      ], 
      re = /([a-z]+)(\:)\s*([a-zA-Z]+)(\()(-?[0-9]*\.?[0-9]+)([a-z]*)(\))(;)?/;
    
  str = str.replace(re, 
   s + tt[0] + m + '$1' + e + 
   s + tt[1] + m + '$2' + e + ' ' + 
   s + tt[2] + m + 
     s + tt[3] + m + '$3' + e + 
     s + tt[1] + m + '$4' + e + 
     s + tt[4] + m + 
       s + tt[5] + "'" + f + '>$5' + e + 
       s + tt[6] + "'" + f + '>$6' + e + e + 
     s + tt[1] + m + '$7' + e + e + 
   s + tt[1] + m + '$8' + e);
      
  return str;
};

var updateAxis = function() {
  axis = this.id.toUpperCase();
  updateCode();
  updateSVG();
};

var updateAngleVal = function() {
  α = rad(sar.value);
  updateCode();
  updateSVG();
};

var updateCode = function() {
  var valsel = '.token--value', 
      valel = codebox.querySelector(valsel);
    
  valel.textContent = 'skew' + axis + 
    '(' + sar.value + 'deg)';
  
  codebox.innerHTML = tokenize(codebox.textContent);
};

var updateSVG = function() {
  var newval = 'skew' + axis + 
      				 '(' + sar.value + ')', 
      sgn = sign(α), d;
  
  target.setAttribute('transform', newval);
  
  if(target.classList) {
    if(target.classList.contains('dir--x') && 
       axis !== 'X') {
      target.classList.remove('dir--x');
      target.classList.add('dir--y');
    }
    if(target.classList.contains('dir--y') && 
       axis !== 'Y') {
      target.classList.remove('dir--y');
      target.classList.add('dir--x');
    }
  }
  else {
    if((target.className.baseVal.indexOf('x') !== -1 || 
        target.className.animVal.indexOf('x') !== -1) && 
       axis !== 'X') {
      target.className.baseVal = 
        target.className.baseVal.replace('x', 'y');
      target.className.animVal = 
        target.className.animVal.replace('x', 'y');
    }
    if((target.className.baseVal.indexOf('y') !== -1 || 
        target.className.animVal.indexOf('y') !== -1) && 
       axis !== 'Y') {
      target.className.baseVal = 
        target.className.baseVal.replace('y', 'x');
      target.className.animVal = 
        target.className.animVal.replace('y', 'x');
    }
  }
  
  if(axis == 'X' && abs(α) > .01) {
    d = 'M' + r*sin(α) + ' ' + r*cos(α) + 
      'A' + r + ' ' + r + 
      ' 0 0 ' + (sgn + 1)/2 + ' 0 ' + r;
    
    if(abs(α) > PI/12) {
      d += 'L' + sgn*15 + ' ' + (r - 10) + 
        'L' + sgn*10 + ' ' + r + 
        'L' + sgn*15 + ' ' + (r + 10) + 
        'L0 ' + r + 
        'A' + r + ' ' + r + 
        ' 0 0 ' + (-sgn + 1)/2 + ' ' + 
        r*sin(α) + ' ' + r*cos(α);
    }
  }
  if(axis === 'Y' && abs(α) > .01) {
    d = 'M' + r*cos(α) + ' ' + r*sin(α) + 
      'A' + r + ' ' + r + 
      ' 0 0 ' + (-sgn + 1)/2 + ' ' + r + ' 0';
    if(abs(α) > PI/12) {
      d += 'L' + (r - 10) + ' ' + sgn*15 + 
        'L' + r + ' ' + sgn*10 + 
        'L' + (r + 10) + ' ' + sgn*15 + 
        'L' + r + ' 0' + 
        'A' + r + ' ' + r + 
        ' 0 0 ' + (sgn + 1)/2 + ' ' + 
        r*cos(α) + ' ' + r*sin(α);
    }
  }
  
  arc.setAttribute('d', d);
  
  updatePoint();
};

var updatePoint = function() {
  var x, y, a, sgn, d = 'M', 
      r1 = ~~p1.getAttribute('r'), 
      r2 = ~~p2.getAttribute('r');
  
  x = ~~p1.getAttribute('cx');
  y = ~~p1.getAttribute('cy');
  
  if(axis === 'X') {   
    a = round(y*tan(α));
    sgn = sign(a);
    d += (x + sgn*r1) + ' ' + y + 'L';
    
    x += a;
    
    d += (x - sgn*r2) + ' ' + y + 
      'L' + (x - sgn*(r2 + 15)) + ' ' + (y - 10) + 
      'L' + (x - sgn*(r2 + 10)) + ' ' + y + 
      'L' + (x - sgn*(r2 + 15)) + ' ' + (y + 10) + 
      'L' + (x - sgn*r2) + ' ' + y;
        
    p2.setAttrs({'cx': x, 'cy': y});
    cl.setAttribute('d', d);
  }
  if(axis === 'Y') {
    a = round(x*tan(α));
    sgn = sign(a);
    d += x + ' ' + (y + sgn*r1) + 'L';
    
    y += a;
    
    d += x + ' ' + (y - sgn*r2) + 
      'L' + (x - 10) + ' ' + (y - sgn*(r2 + 15)) + 
      'L' + x + ' ' + (y - sgn*(r2 + 10)) + 
      'L' + (x + 10) + ' ' + (y - sgn*(r2 + 15)) + 
      'L' + x + ' ' + (y - sgn*r2);
    
    p2.setAttrs({'cx': x, 'cy': y});
    cl.setAttribute('d', d);
  }
  
  if(cl.getTotalLength() < 75) {
    cl.style.opacity = .001;
  }
  else { cl.style.opacity = .999; }
};

codebox.innerHTML = tokenize(codebox.textContent);

sar.addEventListener('change', updateAngleVal, 
                     false);
sar.addEventListener('input', updateAngleVal, 
                     false);
ac[0].addEventListener('change', updateAxis, 
                     false);
ac[1].addEventListener('change', updateAxis, 
                     false);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. //cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js