Search code examples
csstransparencymaskclip

Clipping or chamfering corners of image to see background


I have a design where corners of images are cut in a 45° angle. For the time being, it’s achieved by masking it with an absolutely positioned span, which has a transparent background image set with the corner “cut” in opaque white. This is far from ideal, first because of the additional span, second because the background of the image is not homogeneous white.

I intend to generate transparent PNGs later, but it would be more elegant and – considering the images are photographs – less bandwith-intensive to use JPEGs and CSS. The new CSS mask property seemed promising, but as I understand it does not provide the ability, to „mask through” to the background of the element, does it?

So my question is, is there any new CSS property out there which I’m not aware of and would allow me to do this?


Solution

  • Using Transforms (so CSS3 solution only)

    There is a small amount of imprecision in the following method, and it has two "coding" drawbacks:

    1. Two wrappers needed on the img
    2. Need to know the size of the image (which may not always be a drawback if images are set sizes or if javascript is used to give width/height info).

    It does, however, degrade nicely back to square corners for IE8 and lower.

    The core idea is to size the outer wrapper and hide its overflow, properly size, rotate, and scale down the inner wrapper to create the chamfered corners (which also has overflow hidden), then reverse the rotation and scale back up, and reposition if needed the img nested inside. The method is robust enough to get some fairly decent borders set up if desired, though the rendering of such borders on browsers varies as to quality.

    Here's the fiddle.

    HTML (basic form)

    The span could be a div.

    <span class="chamfer">
        <span>
            <img src="http://placehold.it/351x151" />
        </span>
    </span>
    

    CSS (basic form)

    .chamfer {
        overflow: hidden;
        display: inline-block; /* could be "block" */
        margin: 25px; /* for demo only */
        /* Because of the rotations following, it seems like an odd
           number in width and height worked more consistently, as
           it gives a "middle" pixel by which to transform the rotation
           off of
        */
        width: 351px; /* width of image */
        height: 151px; /* height of image */
    }
    
    .chamfer > span {
        overflow: hidden;
        display: inline-block; /* could be "block" */
        -moz-transform-origin: 50% 50%;
        -webkit-transform-origin: 50% 50%;
        -o-transform-origin: 50% 50%;
        -ms-transform-origin: 50% 50%;
        transform-origin: 50% 50%;
        /* The rotation gets the chamfer angle
           the scale sets the "size" of the cut
           though not very precisely (exact px height
           is not possible to set explicitly.
        */
        -moz-transform: rotate(45deg) scale(.9);
        -webkit-transform: rotate(45deg) scale(.9);
        -o-transform: rotate(45deg) scale(.9);
        -ms-transform: rotate(45deg) scale(.9);
        transform: rotate(45deg) scale(.9);
        /* top/bottom padding is image width (351px)
           minus the image height (151px) = 200px divided by 2;
           if the image were taller than wide, then this
           would become (iH - iW) / 2 for the left/right padding 
        */
        padding: 100px 0; 
        margin-top: -100px; /* adjust for the padding */
        /* the following helped "square" the item better */
        width: 100%;
        height: 100%;
    }
    
    .chamfer img {
        display: inline-block; /* could be "block" */
        -moz-transform-origin: 50% 50%;
        -webkit-transform-origin: 50% 50%;
        -o-transform-origin: 50% 50%;
        -ms-transform-origin: 50% 50%;
        transform-origin: 50% 50%;
        /* The rotation is reversing the wrapper rotation
           to put the image horizontal again, while the scale
           is the inverse of the wrapper's scale, so here
           it is ( 1 / 0.9 ) = 1.11, to scale the image back
           up to correct size
        */
        -moz-transform: rotate(-45deg) scale(1.11);
        -webkit-transform: rotate(-45deg) scale(1.11);
        -o-transform: rotate(-45deg) scale(1.11);
        -ms-transform: rotate(-45deg) scale(1.11);
        transform: rotate(-45deg) scale(1.11);  
    }
    

    HTML (smaller chamfer with 2px border)

    See the above fiddle for a "larger" chamfer with 10px border version as well.

    Of course, if all your images were getting a set sized border, you would just make this just like your base html above, and not do override classes as I have here.

    <span class="chamfer small b2">
        <span>
            <img src="http://placehold.it/351x151" />
        </span>
    </span>
    

    CSS (overrides the basic css above)

    See the above fiddle for a "larger" chamfer with 10px border version as well.

    Of course, if all your images were getting a set sized border, you would just make these the values of your base css, and not do it in separate classes as defined here.

    .b2 * { 
        border: 2px solid black;
    }
    
    .chamfer.b2 { /* 2px border */
        width: 355px; /* 4px added for the 2px border */
        height: 155px; /* 4px added for the 2px border */
    }
    
    .chamfer.b2 > span {
        margin-top: -102px; /* the extra 2px is to accomodate top border of 2px */
        margin-left: -2px; /* this is for the 2px left border */
    }
    
    .chamfer.small > span {
        /* changed the scale for a smaller cut */
        -moz-transform: rotate(45deg) scale(.96);
        -webkit-transform: rotate(45deg) scale(.96);
        -o-transform: rotate(45deg) scale(.96);
        -ms-transform: rotate(45deg) scale(.96);
        transform: rotate(45deg) scale(.96);
    }
    
    .chamfer.small img {
        /* scale changed on wrapper to .96 so scale changes on 
           image to ( 1 / 0.96 ) = 1.042. 
        */
        -moz-transform: rotate(-45deg) scale(1.042);
        -webkit-transform: rotate(-45deg) scale(1.042);
        -o-transform: rotate(-45deg) scale(1.042);
        -ms-transform: rotate(-45deg) scale(1.042);
        transform: rotate(-45deg) scale(1.042);    
    }