hendry hendry - 28 days ago 9
CSS Question

Web cropping interface

I'm trying to emulate avidemux's cropping interface on the Web.

Poster with unwanted black margins

Mixing the colours since our crop has overrun

In https://jsfiddle.net/kaihendry/msL6fjer/ I am using a container and overflow hidden trick to shrink away the black margins. But in this example if you accidentally go lower, you can't actually tell you're eating away at the blue content!

<div :style="'height:' + height + 'px'" class="container">
<img :style="'margin-top: ' + margin + 'px'" width=200 height=200 src=http://s.natalian.org/2016-11-04/200test.png alt="200x200 image with an unwanted margin of 50 either side">
</div>


I re-wrote that example to use clip-path, which frustratingly only seems to work in Chrome 56, but nowhere else! https://jsfiddle.net/kaihendry/nmkh9d39/ This also has the issue of not knowing when you've overshot. Ideally the adjustable red layer grows & when it goes over the blue, the blue+red makes magenta or something like that.

Any tips how to achieve what I want in CSS or SVG? Please feel free to choose different colours!

Answer

The shutter effect that you are after is fairly easy to achieve with SVG.

Here's one way. We create the "shutter" by using two semi-transparent rectangles. Note that I have built my example with jQuery since I am not familiar with Vue.

$slider = $("#slider");

$slider.on("input", update);

function update(evt) {

  var margin = parseInt($slider.val(), 10);
  var top = -margin;
  var bottom = 200 + margin;
  var height = bottom - top;

  $("#margin").text(margin);
  $("#height").text(height);
  $("#toprect").attr("y", top - 100);
  $("#bottomrect").attr("y", bottom);
}

update();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="200" height="200">
  <image width="200" height="200" xlink:href="http://s.natalian.org/2016-11-04/200test.png"/>

  <rect id="toprect" y="-100" width="200" height="100" fill="red" fill-opacity="0.5"/>
  <rect id="bottomrect" y="200" width="200" height="100" fill="red" fill-opacity="0.5"/>
</svg>

<div>
  <label>Margin: <span id="margin"></span></label><br>
  <input id="slider" type="range" max="0" min="-100" value="0"><br>
  <label>Height: <span id="height"></span></label>
</div>

However, in your mockup, your shutter was blended with the background image to create merged colours. We can achieve that effect using an SVG filter.

Here, instead of using <rect> elements, we create the shutter elements using to <feFlood> filter primitives. They allow us to create rectangles of colour which we can then blens with the <image> element we are applying the filter to.

First, just for comparison, here is the exact equivalent of the first sample, implemented using a filter.

$slider = $("#slider");

$slider.on("input", update);

function update(evt) {

  var margin = parseInt($slider.val(), 10);
  var top = -margin;
  var bottom = 200 + margin;
  var height = bottom - top;

  $("#margin").text(margin);
  $("#height").text(height);
  $("#toprect").attr("y", top - 100);
  $("#bottomrect").attr("y", bottom);
}

update();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="200" height="200">
  <defs>
    <filter id="shutter" x="0" y="0" width="200" height="200" filterUnits="userSpaceOnUse">
      <feFlood id="toprect" x="0" y="0" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part1"/>
      <feFlood id="bottomrect" x="0" y="100" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part2"/>
      <feMerge>
        <feMergeNode in="SourceGraphic"/>
        <feMergeNode in="part1"/>
        <feMergeNode in="part2"/>
      </feMerge>
    </filter>
  </defs>

  <image width="200" height="200" xlink:href="http://s.natalian.org/2016-11-04/200test.png"
         filter="url(#shutter)"/>
</svg>

<div>
  <label>Margin: <span id="margin"></span></label><br>
  <input id="slider" type="range" max="0" min="-100" value="0"><br>
  <label>Height: <span id="height"></span></label>
</div>

Now to achieve your blending effect, we need to change the way we combine the shutter elements with the image. We do that with the <feBlend mode="screen"> filter primitive.

$slider = $("#slider");

$slider.on("input", update);

function update(evt) {

  var margin = parseInt($slider.val(), 10);
  var top = -margin;
  var bottom = 200 + margin;
  var height = bottom - top;

  $("#margin").text(margin);
  $("#height").text(height);
  $("#toprect").attr("y", top - 100);
  $("#bottomrect").attr("y", bottom);
}

update();
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="200" height="200">
  <defs>
    <filter id="shutter" x="0" y="0" width="200" height="200" filterUnits="userSpaceOnUse">
      <feFlood id="toprect" x="0" y="0" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part1"/>
      <feFlood id="bottomrect" x="0" y="100" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part2"/>
      <feBlend mode="screen" in="SourceGraphic" in2="part1"/>
      <feBlend mode="screen" in2="part2"/>
    </filter>
  </defs>

  <image width="200" height="200" xlink:href="http://s.natalian.org/2016-11-04/200test.png"
         filter="url(#shutter)"/>
</svg>

<div>
  <label>Margin: <span id="margin"></span></label><br>
  <input id="slider" type="range" max="0" min="-100" value="0"><br>
  <label>Height: <span id="height"></span></label>
</div>

Update: solution for <video>

You didn't mention that you wanted to achieve this over live video. For that you would need a slightly different solution because the <video> element is not supported in the current SVG 1.1 standard. It will likely be in the upcoming SVG 2 standard though.

Luckily it doesn't require a big change to support video. You just need to have the <video> element in your HTML. You can still apply an SVG filter to it.

This will work in some browsers. For example Chrome and Firefox, but not in others like IE11.

$slider = $("#slider");

$slider.on("input", update);

function update(evt) {

  var margin = parseInt($slider.val(), 10);
  var top = -margin;
  var bottom = 200 + margin;
  var height = bottom - top;

  $("#margin").text(margin);
  $("#height").text(height);
  $("#toprect").attr("y", top - 100);
  $("#bottomrect").attr("y", bottom);
}

update();
video {
  filter: url(#shutter);
}

.container {
  width: 200px;
  height: 200px;
  overflow: hidden;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<svg width="0" height="0">
  <defs>
    <filter id="shutter" x="0" y="0" width="200" height="200" filterUnits="userSpaceOnUse">
      <feFlood id="toprect" x="0" y="0" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part1"/>
      <feFlood id="bottomrect" x="0" y="100" width="200" height="100" flood-color="red" flood-opacity="0.5"
               result="part2"/>
      <feBlend mode="screen" in="SourceGraphic" in2="part1"/>
      <feBlend mode="screen" in2="part2"/>
    </filter>
  </defs>

</svg>

<div class="container">
  <video width="200" height="200" src="http://www.w3schools.com/html/mov_bbb.mp4"
         autoplay loop></video>
</div>

<div>
  <label>Margin: <span id="margin"></span></label><br>
  <input id="slider" type="range" max="0" min="-100" value="0"><br>
  <label>Height: <span id="height"></span></label>
</div>